Repository: alibaba/Sentinel Branch: 1.8 Commit: 38b4619a8c4a Files: 1604 Total size: 5.1 MB Directory structure: gitextract_7y3y0mhf/ ├── .codecov.yml ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci.yml │ ├── codeql-analysis.yml │ └── document-lint.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── doc/ │ ├── README.md │ └── awesome-sentinel.md ├── pom.xml ├── sentinel-adapter/ │ ├── pom.xml │ ├── sentinel-apache-dubbo-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── dubbo/ │ │ │ │ ├── BaseSentinelDubboFilter.java │ │ │ │ ├── DubboAppContextFilter.java │ │ │ │ ├── DubboUtils.java │ │ │ │ ├── SentinelDubboConsumerFilter.java │ │ │ │ ├── SentinelDubboProviderFilter.java │ │ │ │ ├── config/ │ │ │ │ │ └── DubboAdapterGlobalConfig.java │ │ │ │ ├── fallback/ │ │ │ │ │ ├── DefaultDubboFallback.java │ │ │ │ │ ├── DubboFallback.java │ │ │ │ │ └── DubboFallbackRegistry.java │ │ │ │ └── origin/ │ │ │ │ ├── DefaultDubboOriginParser.java │ │ │ │ └── DubboOriginParser.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── dubbo/ │ │ │ └── org.apache.dubbo.rpc.Filter │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ ├── BaseTest.java │ │ │ ├── DubboTestUtil.java │ │ │ └── adapter/ │ │ │ └── dubbo/ │ │ │ ├── AbstractTimeBasedTest.java │ │ │ ├── DubboAppContextFilterTest.java │ │ │ ├── DubboUtilsTest.java │ │ │ ├── SentinelDubboConsumerFilterTest.java │ │ │ ├── SentinelDubboProviderFilterTest.java │ │ │ ├── fallback/ │ │ │ │ └── DubboFallbackRegistryTest.java │ │ │ ├── origin/ │ │ │ │ └── DubboOriginRegistryTest.java │ │ │ └── provider/ │ │ │ ├── DemoService.java │ │ │ └── impl/ │ │ │ └── DemoServiceImpl.java │ │ └── resources/ │ │ ├── spring-dubbo-consumer-filter.xml │ │ └── spring-dubbo-provider-filter.xml │ ├── sentinel-apache-dubbo3-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── dubbo3/ │ │ │ │ ├── BaseSentinelDubboFilter.java │ │ │ │ ├── DubboAppContextFilter.java │ │ │ │ ├── DubboUtils.java │ │ │ │ ├── SentinelDubboConsumerFilter.java │ │ │ │ ├── SentinelDubboProviderFilter.java │ │ │ │ ├── config/ │ │ │ │ │ └── DubboAdapterGlobalConfig.java │ │ │ │ ├── fallback/ │ │ │ │ │ ├── DefaultDubboFallback.java │ │ │ │ │ ├── DubboFallback.java │ │ │ │ │ └── DubboFallbackRegistry.java │ │ │ │ └── origin/ │ │ │ │ ├── DefaultDubboOriginParser.java │ │ │ │ └── DubboOriginParser.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── dubbo/ │ │ │ ├── org.apache.dubbo.rpc.Filter │ │ │ └── org.apache.dubbo.rpc.cluster.filter.ClusterFilter │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ ├── BaseTest.java │ │ │ ├── DubboTestUtil.java │ │ │ └── adapter/ │ │ │ └── dubbo3/ │ │ │ ├── AbstractTimeBasedTest.java │ │ │ ├── DubboAppContextFilterTest.java │ │ │ ├── DubboUtilsTest.java │ │ │ ├── SentinelDubboConsumerFilterTest.java │ │ │ ├── SentinelDubboProviderFilterTest.java │ │ │ ├── SentinelFilterTest.java │ │ │ ├── fallback/ │ │ │ │ └── DubboFallbackRegistryTest.java │ │ │ ├── origin/ │ │ │ │ └── DubboOriginRegistryTest.java │ │ │ └── provider/ │ │ │ ├── DemoService.java │ │ │ └── impl/ │ │ │ └── DemoServiceImpl.java │ │ └── resources/ │ │ ├── spring-dubbo-consumer-filter.xml │ │ └── spring-dubbo-provider-filter.xml │ ├── sentinel-apache-httpclient-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── apache/ │ │ │ └── httpclient/ │ │ │ ├── SentinelApacheHttpClientBuilder.java │ │ │ ├── config/ │ │ │ │ └── SentinelApacheHttpClientConfig.java │ │ │ ├── extractor/ │ │ │ │ ├── ApacheHttpClientResourceExtractor.java │ │ │ │ └── DefaultApacheHttpClientResourceExtractor.java │ │ │ └── fallback/ │ │ │ ├── ApacheHttpClientFallback.java │ │ │ └── DefaultApacheHttpClientFallback.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── apache/ │ │ └── httpclient/ │ │ ├── SentinelApacheHttpClientTest.java │ │ ├── app/ │ │ │ ├── TestApplication.java │ │ │ └── controller/ │ │ │ └── TestController.java │ │ ├── config/ │ │ │ └── SentinelApacheHttpClientConfigTest.java │ │ └── fallback/ │ │ └── ApacheHttpClientFallbackTest.java │ ├── sentinel-api-gateway-adapter-common/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── gateway/ │ │ │ │ └── common/ │ │ │ │ ├── SentinelGatewayConstants.java │ │ │ │ ├── api/ │ │ │ │ │ ├── ApiDefinition.java │ │ │ │ │ ├── ApiDefinitionChangeObserver.java │ │ │ │ │ ├── ApiPathPredicateItem.java │ │ │ │ │ ├── ApiPredicateGroupItem.java │ │ │ │ │ ├── ApiPredicateItem.java │ │ │ │ │ ├── GatewayApiDefinitionManager.java │ │ │ │ │ └── matcher/ │ │ │ │ │ └── AbstractApiMatcher.java │ │ │ │ ├── command/ │ │ │ │ │ ├── GetGatewayApiDefinitionGroupCommandHandler.java │ │ │ │ │ ├── GetGatewayRuleCommandHandler.java │ │ │ │ │ ├── UpdateGatewayApiDefinitionGroupCommandHandler.java │ │ │ │ │ └── UpdateGatewayRuleCommandHandler.java │ │ │ │ ├── param/ │ │ │ │ │ ├── ConfigurableRequestItemParser.java │ │ │ │ │ ├── GatewayParamParser.java │ │ │ │ │ ├── GatewayRegexCache.java │ │ │ │ │ └── RequestItemParser.java │ │ │ │ ├── rule/ │ │ │ │ │ ├── GatewayFlowRule.java │ │ │ │ │ ├── GatewayParamFlowItem.java │ │ │ │ │ ├── GatewayRuleConverter.java │ │ │ │ │ └── GatewayRuleManager.java │ │ │ │ └── slot/ │ │ │ │ ├── GatewayFlowSlot.java │ │ │ │ └── GatewaySlotChainBuilder.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ │ └── com.alibaba.csp.sentinel.slotchain.ProcessorSlot │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── gateway/ │ │ └── common/ │ │ ├── api/ │ │ │ └── GatewayApiDefinitionManagerTest.java │ │ ├── param/ │ │ │ ├── GatewayParamParserTest.java │ │ │ └── GatewayRegexCacheTest.java │ │ └── rule/ │ │ ├── GatewayRuleConverterTest.java │ │ └── GatewayRuleManagerTest.java │ ├── sentinel-dubbo-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── dubbo/ │ │ │ │ ├── AbstractDubboFilter.java │ │ │ │ ├── DubboAdapterGlobalConfig.java │ │ │ │ ├── DubboAppContextFilter.java │ │ │ │ ├── DubboUtils.java │ │ │ │ ├── SentinelDubboConsumerFilter.java │ │ │ │ ├── SentinelDubboProviderFilter.java │ │ │ │ ├── fallback/ │ │ │ │ │ ├── DefaultDubboFallback.java │ │ │ │ │ ├── DubboFallback.java │ │ │ │ │ └── DubboFallbackRegistry.java │ │ │ │ └── origin/ │ │ │ │ ├── DefaultDubboOriginParser.java │ │ │ │ └── DubboOriginParser.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── dubbo/ │ │ │ └── com.alibaba.dubbo.rpc.Filter │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ ├── BaseTest.java │ │ │ └── adapter/ │ │ │ └── dubbo/ │ │ │ ├── AbstractDubboFilterTest.java │ │ │ ├── DubboAppContextFilterTest.java │ │ │ ├── DubboUtilsTest.java │ │ │ ├── SentinelDubboConsumerFilterTest.java │ │ │ ├── SentinelDubboProviderFilterTest.java │ │ │ ├── fallback/ │ │ │ │ └── DubboFallbackRegistryTest.java │ │ │ ├── origin/ │ │ │ │ └── DubboOriginRegistryTest.java │ │ │ └── provider/ │ │ │ ├── DemoService.java │ │ │ └── impl/ │ │ │ └── DemoServiceImpl.java │ │ └── resources/ │ │ ├── spring-dubbo-consumer-filter.xml │ │ └── spring-dubbo-provider-filter.xml │ ├── sentinel-grpc-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── grpc/ │ │ │ ├── SentinelGrpcClientInterceptor.java │ │ │ └── SentinelGrpcServerInterceptor.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── grpc/ │ │ │ ├── FooServiceClient.java │ │ │ ├── FooServiceImpl.java │ │ │ ├── GrpcTestServer.java │ │ │ ├── SentinelGrpcClientInterceptorTest.java │ │ │ └── SentinelGrpcServerInterceptorTest.java │ │ └── proto/ │ │ └── example.proto │ ├── sentinel-jax-rs-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── jaxrs/ │ │ │ ├── SentinelJaxRsClientTemplate.java │ │ │ ├── SentinelJaxRsProviderFilter.java │ │ │ ├── config/ │ │ │ │ └── SentinelJaxRsConfig.java │ │ │ ├── exception/ │ │ │ │ └── DefaultExceptionMapper.java │ │ │ ├── fallback/ │ │ │ │ ├── DefaultSentinelJaxRsFallback.java │ │ │ │ └── SentinelJaxRsFallback.java │ │ │ ├── future/ │ │ │ │ └── FutureWrapper.java │ │ │ └── request/ │ │ │ ├── DefaultRequestOriginParser.java │ │ │ ├── DefaultResourceNameParser.java │ │ │ ├── RequestOriginParser.java │ │ │ └── ResourceNameParser.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── jaxrs/ │ │ │ ├── ClientFilterTest.java │ │ │ ├── ProviderFilterTest.java │ │ │ ├── TestApplication.java │ │ │ └── TestResource.java │ │ └── resources/ │ │ ├── application-client.yml │ │ └── application-provider.yml │ ├── sentinel-motan-adapter/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── motan/ │ │ │ ├── MotanUtils.java │ │ │ ├── SentinelMotanConsumerFilter.java │ │ │ ├── SentinelMotanProviderFilter.java │ │ │ ├── config/ │ │ │ │ └── MotanAdapterGlobalConfig.java │ │ │ └── fallback/ │ │ │ ├── DefaultMotanFallback.java │ │ │ └── MotanFallback.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.weibo.api.motan.filter.Filter │ ├── sentinel-okhttp-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── okhttp/ │ │ │ ├── SentinelOkHttpConfig.java │ │ │ ├── SentinelOkHttpInterceptor.java │ │ │ ├── extractor/ │ │ │ │ ├── DefaultOkHttpResourceExtractor.java │ │ │ │ └── OkHttpResourceExtractor.java │ │ │ └── fallback/ │ │ │ ├── DefaultOkHttpFallback.java │ │ │ └── OkHttpFallback.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── okhttp/ │ │ ├── SentinelOkHttpInterceptorTest.java │ │ ├── app/ │ │ │ ├── TestApplication.java │ │ │ └── controller/ │ │ │ └── TestController.java │ │ ├── config/ │ │ │ └── SentinelOkHttpConfigTest.java │ │ ├── extractor/ │ │ │ └── OkHttpResourceExtractorTest.java │ │ └── fallback/ │ │ └── OkHttpFallbackTest.java │ ├── sentinel-quarkus-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ ├── sentinel-annotation-quarkus-adapter-deployment/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── quarkus/ │ │ │ │ └── annotation/ │ │ │ │ └── deployment/ │ │ │ │ └── SentinelAnnotationQuarkusAdapterProcessor.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── quarkus/ │ │ │ └── annotation/ │ │ │ └── deployment/ │ │ │ ├── FooService.java │ │ │ ├── FooUtil.java │ │ │ └── SentinelAnnotationQuarkusAdapterTest.java │ │ ├── sentinel-annotation-quarkus-adapter-runtime/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── quarkus-extension.yaml │ │ ├── sentinel-jax-rs-quarkus-adapter-deployment/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── quarkus/ │ │ │ │ └── jaxrs/ │ │ │ │ └── deployment/ │ │ │ │ └── SentinelJaxRsQuarkusAdapterProcessor.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── quarkus/ │ │ │ └── jaxrs/ │ │ │ └── deployment/ │ │ │ ├── SentinelJaxRsQuarkusAdapterTest.java │ │ │ └── TestResource.java │ │ ├── sentinel-jax-rs-quarkus-adapter-runtime/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── quarkus-extension.yaml │ │ │ └── services/ │ │ │ └── javax.ws.rs.ext.Providers │ │ ├── sentinel-native-image-quarkus-adapter-deployment/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── quarkus/ │ │ │ └── nativeimage/ │ │ │ └── SentinelNativeImageProcessor.java │ │ └── sentinel-native-image-quarkus-adapter-runtime/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── quarkus/ │ │ │ └── nativeimage/ │ │ │ └── SentinelRecorder.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── quarkus-extension.yaml │ ├── sentinel-reactor-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── reactor/ │ │ │ ├── ContextConfig.java │ │ │ ├── EntryConfig.java │ │ │ ├── FluxSentinelOperator.java │ │ │ ├── InheritableBaseSubscriber.java │ │ │ ├── MonoSentinelOperator.java │ │ │ ├── ReactorSphU.java │ │ │ ├── SentinelReactorConstants.java │ │ │ ├── SentinelReactorSubscriber.java │ │ │ └── SentinelReactorTransformer.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── reactor/ │ │ ├── FluxSentinelOperatorTestIntegrationTest.java │ │ ├── MonoSentinelOperatorIntegrationTest.java │ │ └── ReactorSphUTest.java │ ├── sentinel-sofa-rpc-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── sofa/ │ │ │ │ └── rpc/ │ │ │ │ ├── AbstractSofaRpcFilter.java │ │ │ │ ├── SentinelConstants.java │ │ │ │ ├── SentinelSofaRpcConsumerFilter.java │ │ │ │ ├── SentinelSofaRpcProviderFilter.java │ │ │ │ ├── SofaRpcUtils.java │ │ │ │ └── fallback/ │ │ │ │ ├── DefaultSofaRpcFallback.java │ │ │ │ ├── SofaRpcFallback.java │ │ │ │ └── SofaRpcFallbackRegistry.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── sofa-rpc/ │ │ │ └── com.alipay.sofa.rpc.filter.Filter │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── sofa/ │ │ └── rpc/ │ │ ├── AbstractSofaRpcFilterTest.java │ │ ├── BaseTest.java │ │ ├── SentinelSofaRpcConsumerFilterTest.java │ │ ├── SentinelSofaRpcProviderFilterTest.java │ │ ├── SofaRpcUtilsTest.java │ │ ├── fallback/ │ │ │ ├── DefaultSofaRpcFallbackTest.java │ │ │ └── SofaRpcFallbackRegistryTest.java │ │ └── service/ │ │ ├── DemoService.java │ │ └── impl/ │ │ └── DemoServiceImpl.java │ ├── sentinel-spring-cloud-gateway-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── gateway/ │ │ │ │ └── sc/ │ │ │ │ ├── SentinelGatewayFilter.java │ │ │ │ ├── ServerWebExchangeItemParser.java │ │ │ │ ├── api/ │ │ │ │ │ ├── GatewayApiMatcherManager.java │ │ │ │ │ ├── SpringCloudGatewayApiDefinitionChangeObserver.java │ │ │ │ │ └── matcher/ │ │ │ │ │ └── WebExchangeApiMatcher.java │ │ │ │ ├── callback/ │ │ │ │ │ ├── BlockRequestHandler.java │ │ │ │ │ ├── DefaultBlockRequestHandler.java │ │ │ │ │ ├── GatewayCallbackManager.java │ │ │ │ │ └── RedirectBlockRequestHandler.java │ │ │ │ ├── exception/ │ │ │ │ │ └── SentinelGatewayBlockExceptionHandler.java │ │ │ │ └── route/ │ │ │ │ ├── AntRoutePathMatcher.java │ │ │ │ ├── RegexRoutePathMatcher.java │ │ │ │ └── RouteMatchers.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── gateway/ │ │ │ └── sc/ │ │ │ ├── SentinelGatewayFilterTest.java │ │ │ └── SpringCloudGatewayParamParserTest.java │ │ └── resources/ │ │ └── mockito-extensions/ │ │ └── org.mockito.plugins.MockMaker │ ├── sentinel-spring-cloud-gateway-v6x-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── gateway/ │ │ │ │ └── sc/ │ │ │ │ ├── SentinelGatewayFilter.java │ │ │ │ ├── ServerWebExchangeItemParser.java │ │ │ │ ├── api/ │ │ │ │ │ ├── GatewayApiMatcherManager.java │ │ │ │ │ ├── SpringCloudGatewayApiDefinitionChangeObserver.java │ │ │ │ │ └── matcher/ │ │ │ │ │ └── WebExchangeApiMatcher.java │ │ │ │ ├── callback/ │ │ │ │ │ ├── BlockRequestHandler.java │ │ │ │ │ ├── DefaultBlockRequestHandler.java │ │ │ │ │ ├── GatewayCallbackManager.java │ │ │ │ │ └── RedirectBlockRequestHandler.java │ │ │ │ ├── exception/ │ │ │ │ │ └── SentinelGatewayBlockExceptionHandler.java │ │ │ │ └── route/ │ │ │ │ ├── AntRoutePathMatcher.java │ │ │ │ ├── RegexRoutePathMatcher.java │ │ │ │ └── RouteMatchers.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── gateway/ │ │ │ └── sc/ │ │ │ ├── SentinelGatewayFilterTest.java │ │ │ └── SpringCloudGatewayParamParserTest.java │ │ └── resources/ │ │ └── mockito-extensions/ │ │ └── org.mockito.plugins.MockMaker │ ├── sentinel-spring-restclient-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── spring/ │ │ │ └── restclient/ │ │ │ ├── SentinelRestClientConfig.java │ │ │ ├── SentinelRestClientInterceptor.java │ │ │ ├── extractor/ │ │ │ │ ├── DefaultRestClientResourceExtractor.java │ │ │ │ └── RestClientResourceExtractor.java │ │ │ └── fallback/ │ │ │ ├── DefaultRestClientFallback.java │ │ │ └── RestClientFallback.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── spring/ │ │ └── restclient/ │ │ ├── ManualTest.java │ │ ├── SentinelRestClientConfigTest.java │ │ ├── SentinelRestClientInterceptorSimpleTest.java │ │ ├── app/ │ │ │ ├── TestApplication.java │ │ │ └── TestController.java │ │ ├── extractor/ │ │ │ └── DefaultRestClientResourceExtractorTest.java │ │ └── fallback/ │ │ └── DefaultRestClientFallbackTest.java │ ├── sentinel-spring-webflux-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── spring/ │ │ │ └── webflux/ │ │ │ ├── SentinelWebFluxFilter.java │ │ │ ├── callback/ │ │ │ │ ├── BlockRequestHandler.java │ │ │ │ ├── DefaultBlockRequestHandler.java │ │ │ │ └── WebFluxCallbackManager.java │ │ │ └── exception/ │ │ │ └── SentinelBlockExceptionHandler.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── spring/ │ │ └── webflux/ │ │ ├── SentinelWebFluxIntegrationTest.java │ │ └── test/ │ │ ├── WebFluxTestApplication.java │ │ ├── WebFluxTestConfig.java │ │ ├── WebFluxTestController.java │ │ └── WebFluxTestRouter.java │ ├── sentinel-spring-webmvc-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── spring/ │ │ │ └── webmvc/ │ │ │ ├── AbstractSentinelInterceptor.java │ │ │ ├── SentinelExceptionAware.java │ │ │ ├── SentinelWebInterceptor.java │ │ │ ├── SentinelWebTotalInterceptor.java │ │ │ ├── callback/ │ │ │ │ ├── BlockExceptionHandler.java │ │ │ │ ├── DefaultBlockExceptionHandler.java │ │ │ │ ├── RequestOriginParser.java │ │ │ │ └── UrlCleaner.java │ │ │ └── config/ │ │ │ ├── BaseWebMvcConfig.java │ │ │ ├── SentinelWebMvcConfig.java │ │ │ └── SentinelWebMvcTotalConfig.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── spring/ │ │ └── webmvc/ │ │ ├── ResultWrapper.java │ │ ├── SentinelSpringMvcIntegrationTest.java │ │ ├── SentinelWebInterceptorTest.java │ │ ├── TestApplication.java │ │ ├── config/ │ │ │ ├── InterceptorConfig.java │ │ │ └── SentinelSpringMvcBlockHandlerConfig.java │ │ ├── controller/ │ │ │ └── TestController.java │ │ └── exception/ │ │ └── BizException.java │ ├── sentinel-spring-webmvc-v6x-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── spring/ │ │ │ └── webmvc_v6x/ │ │ │ ├── AbstractSentinelInterceptor.java │ │ │ ├── SentinelWebInterceptor.java │ │ │ ├── SentinelWebPrefixInterceptor.java │ │ │ ├── SentinelWebTotalInterceptor.java │ │ │ ├── callback/ │ │ │ │ ├── BlockExceptionHandler.java │ │ │ │ ├── DefaultBlockExceptionHandler.java │ │ │ │ └── RequestOriginParser.java │ │ │ └── config/ │ │ │ ├── BaseWebMvcConfig.java │ │ │ ├── SentinelPreWebMvcConfig.java │ │ │ ├── SentinelWebMvcConfig.java │ │ │ ├── SentinelWebMvcTotalConfig.java │ │ │ └── WebServletLocalConfig.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── spring/ │ │ └── webmvc_v6x/ │ │ ├── ResultWrapper.java │ │ ├── SentinelSpringMvcIntegrationTest.java │ │ ├── SentinelWebInterceptorHttpMethodPrefixTest.java │ │ ├── SentinelWebInterceptorTest.java │ │ ├── TestApplication.java │ │ ├── callback/ │ │ │ └── DefaultBlockExceptionHandlerTest.java │ │ ├── config/ │ │ │ ├── InterceptorConfig.java │ │ │ └── SentinelSpringMvcBlockHandlerConfig.java │ │ └── controller/ │ │ └── TestController.java │ ├── sentinel-web-adapter-common/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── web/ │ │ └── common/ │ │ └── UrlCleaner.java │ ├── sentinel-web-servlet/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── servlet/ │ │ │ ├── CommonFilter.java │ │ │ ├── CommonTotalFilter.java │ │ │ ├── callback/ │ │ │ │ ├── DefaultUrlBlockHandler.java │ │ │ │ ├── DefaultUrlCleaner.java │ │ │ │ ├── RequestOriginParser.java │ │ │ │ ├── UrlBlockHandler.java │ │ │ │ ├── UrlCleaner.java │ │ │ │ └── WebCallbackManager.java │ │ │ ├── config/ │ │ │ │ └── WebServletConfig.java │ │ │ └── util/ │ │ │ └── FilterUtil.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ ├── servlet/ │ │ │ ├── CommonFilterTest.java │ │ │ ├── FilterConfig.java │ │ │ ├── TestApplication.java │ │ │ └── TestController.java │ │ ├── servletcontext/ │ │ │ ├── CommonFilterContextTest.java │ │ │ ├── FilterContextConfig.java │ │ │ ├── TestContextApplication.java │ │ │ └── TestContextController.java │ │ └── servletmethod/ │ │ ├── CommonFilterMethodTest.java │ │ ├── FilterMethodConfig.java │ │ ├── TestApplication.java │ │ └── TestMethodController.java │ ├── sentinel-zuul-adapter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── adapter/ │ │ │ │ └── gateway/ │ │ │ │ └── zuul/ │ │ │ │ ├── RequestContextItemParser.java │ │ │ │ ├── api/ │ │ │ │ │ ├── ZuulApiDefinitionChangeObserver.java │ │ │ │ │ ├── ZuulGatewayApiMatcherManager.java │ │ │ │ │ ├── matcher/ │ │ │ │ │ │ └── RequestContextApiMatcher.java │ │ │ │ │ └── route/ │ │ │ │ │ ├── PrefixRoutePathMatcher.java │ │ │ │ │ ├── RegexRoutePathMatcher.java │ │ │ │ │ └── ZuulRouteMatchers.java │ │ │ │ ├── callback/ │ │ │ │ │ ├── DefaultRequestOriginParser.java │ │ │ │ │ ├── RequestOriginParser.java │ │ │ │ │ └── ZuulGatewayCallbackManager.java │ │ │ │ ├── constants/ │ │ │ │ │ └── ZuulConstant.java │ │ │ │ ├── fallback/ │ │ │ │ │ ├── BlockResponse.java │ │ │ │ │ ├── DefaultBlockFallbackProvider.java │ │ │ │ │ ├── ZuulBlockFallbackManager.java │ │ │ │ │ └── ZuulBlockFallbackProvider.java │ │ │ │ └── filters/ │ │ │ │ ├── EntryHolder.java │ │ │ │ ├── SentinelEntryUtils.java │ │ │ │ ├── SentinelZuulErrorFilter.java │ │ │ │ ├── SentinelZuulPostFilter.java │ │ │ │ └── SentinelZuulPreFilter.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── adapter/ │ │ └── gateway/ │ │ └── zuul/ │ │ ├── fallback/ │ │ │ ├── ZuulBlockFallbackManagerTest.java │ │ │ └── ZuulBlockFallbackProviderTest.java │ │ ├── filters/ │ │ │ ├── SentinelZuulErrorFilterTest.java │ │ │ ├── SentinelZuulPostFilterTest.java │ │ │ └── SentinelZuulPreFilterTest.java │ │ └── route/ │ │ └── SentinelZuulRouteTest.java │ └── sentinel-zuul2-adapter/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── adapter/ │ │ │ └── gateway/ │ │ │ └── zuul2/ │ │ │ ├── HttpRequestMessageItemParser.java │ │ │ ├── api/ │ │ │ │ ├── ZuulApiDefinitionChangeObserver.java │ │ │ │ ├── ZuulGatewayApiMatcherManager.java │ │ │ │ ├── matcher/ │ │ │ │ │ └── HttpRequestMessageApiMatcher.java │ │ │ │ └── route/ │ │ │ │ ├── PrefixRoutePathMatcher.java │ │ │ │ ├── RegexRoutePathMatcher.java │ │ │ │ └── ZuulRouteMatchers.java │ │ │ ├── constants/ │ │ │ │ └── SentinelZuul2Constants.java │ │ │ ├── fallback/ │ │ │ │ ├── BlockResponse.java │ │ │ │ ├── DefaultBlockFallbackProvider.java │ │ │ │ ├── ZuulBlockFallbackManager.java │ │ │ │ └── ZuulBlockFallbackProvider.java │ │ │ └── filters/ │ │ │ ├── EntryHolder.java │ │ │ ├── endpoint/ │ │ │ │ └── SentinelZuulEndpoint.java │ │ │ ├── inbound/ │ │ │ │ └── SentinelZuulInboundFilter.java │ │ │ └── outbound/ │ │ │ └── SentinelZuulOutboundFilter.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── adapter/ │ └── gateway/ │ └── zuul2/ │ └── fallback/ │ ├── ZuulBlockFallbackManagerTest.java │ └── ZuulBlockFallbackProviderTest.java ├── sentinel-benchmark/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── benchmark/ │ └── SentinelEntryBenchmark.java ├── sentinel-cluster/ │ ├── README.md │ ├── pom.xml │ ├── sentinel-cluster-client-default/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ ├── cluster/ │ │ │ │ │ └── client/ │ │ │ │ │ ├── ClientConstants.java │ │ │ │ │ ├── DefaultClusterTokenClient.java │ │ │ │ │ ├── NettyTransportClient.java │ │ │ │ │ ├── codec/ │ │ │ │ │ │ ├── ClientEntityCodecProvider.java │ │ │ │ │ │ ├── DefaultRequestEntityWriter.java │ │ │ │ │ │ ├── DefaultResponseEntityDecoder.java │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ ├── FlowRequestDataWriter.java │ │ │ │ │ │ │ ├── FlowResponseDataDecoder.java │ │ │ │ │ │ │ ├── ParamFlowRequestDataWriter.java │ │ │ │ │ │ │ ├── PingRequestDataWriter.java │ │ │ │ │ │ │ └── PingResponseDataDecoder.java │ │ │ │ │ │ ├── netty/ │ │ │ │ │ │ │ ├── NettyRequestEncoder.java │ │ │ │ │ │ │ └── NettyResponseDecoder.java │ │ │ │ │ │ └── registry/ │ │ │ │ │ │ ├── RequestDataWriterRegistry.java │ │ │ │ │ │ └── ResponseDataDecodeRegistry.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ClusterClientAssignConfig.java │ │ │ │ │ │ ├── ClusterClientConfig.java │ │ │ │ │ │ ├── ClusterClientConfigManager.java │ │ │ │ │ │ ├── ClusterClientStartUpConfig.java │ │ │ │ │ │ └── ServerChangeObserver.java │ │ │ │ │ ├── handler/ │ │ │ │ │ │ ├── TokenClientHandler.java │ │ │ │ │ │ └── TokenClientPromiseHolder.java │ │ │ │ │ └── init/ │ │ │ │ │ └── DefaultClusterClientInitFunc.java │ │ │ │ └── command/ │ │ │ │ ├── entity/ │ │ │ │ │ └── ClusterClientStateEntity.java │ │ │ │ └── handler/ │ │ │ │ ├── FetchClusterClientConfigHandler.java │ │ │ │ └── ModifyClusterClientConfigHandler.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient │ │ │ ├── com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter │ │ │ ├── com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder │ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── cluster/ │ │ └── client/ │ │ └── codec/ │ │ └── data/ │ │ ├── FlowResponseDataDecoderTest.java │ │ ├── ParamFlowRequestDataWriterTest.java │ │ └── PingResponseDataDecoderTest.java │ ├── sentinel-cluster-common-default/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── cluster/ │ │ ├── ClusterConstants.java │ │ ├── ClusterErrorMessages.java │ │ ├── ClusterTransportClient.java │ │ ├── annotation/ │ │ │ └── RequestType.java │ │ ├── codec/ │ │ │ ├── EntityDecoder.java │ │ │ ├── EntityWriter.java │ │ │ ├── request/ │ │ │ │ ├── RequestEntityDecoder.java │ │ │ │ └── RequestEntityWriter.java │ │ │ └── response/ │ │ │ ├── ResponseEntityDecoder.java │ │ │ └── ResponseEntityWriter.java │ │ ├── exception/ │ │ │ └── SentinelClusterException.java │ │ ├── registry/ │ │ │ └── ConfigSupplierRegistry.java │ │ ├── request/ │ │ │ ├── ClusterRequest.java │ │ │ ├── Request.java │ │ │ └── data/ │ │ │ ├── FlowRequestData.java │ │ │ └── ParamFlowRequestData.java │ │ └── response/ │ │ ├── ClusterResponse.java │ │ ├── Response.java │ │ └── data/ │ │ └── FlowTokenResponseData.java │ ├── sentinel-cluster-server-default/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── cluster/ │ │ │ │ ├── flow/ │ │ │ │ │ ├── ClusterFlowChecker.java │ │ │ │ │ ├── ClusterParamFlowChecker.java │ │ │ │ │ ├── ConcurrentClusterFlowChecker.java │ │ │ │ │ ├── DefaultTokenService.java │ │ │ │ │ ├── rule/ │ │ │ │ │ │ ├── ClusterFlowRuleManager.java │ │ │ │ │ │ ├── ClusterParamFlowRuleManager.java │ │ │ │ │ │ └── NamespaceFlowProperty.java │ │ │ │ │ └── statistic/ │ │ │ │ │ ├── ClusterMetricNode.java │ │ │ │ │ ├── ClusterMetricNodeGenerator.java │ │ │ │ │ ├── ClusterMetricStatistics.java │ │ │ │ │ ├── ClusterParamMetricStatistics.java │ │ │ │ │ ├── concurrent/ │ │ │ │ │ │ ├── ClusterConcurrentCheckerLogListener.java │ │ │ │ │ │ ├── CurrentConcurrencyManager.java │ │ │ │ │ │ ├── TokenCacheNode.java │ │ │ │ │ │ ├── TokenCacheNodeManager.java │ │ │ │ │ │ └── expire/ │ │ │ │ │ │ ├── ExpireStrategy.java │ │ │ │ │ │ └── RegularExpireStrategy.java │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── ClusterFlowEvent.java │ │ │ │ │ │ └── ClusterMetricBucket.java │ │ │ │ │ ├── limit/ │ │ │ │ │ │ ├── GlobalRequestLimiter.java │ │ │ │ │ │ └── RequestLimiter.java │ │ │ │ │ └── metric/ │ │ │ │ │ ├── ClusterMetric.java │ │ │ │ │ ├── ClusterMetricLeapArray.java │ │ │ │ │ ├── ClusterParamMetric.java │ │ │ │ │ └── ClusterParameterLeapArray.java │ │ │ │ └── server/ │ │ │ │ ├── DefaultEmbeddedTokenServer.java │ │ │ │ ├── NettyTransportServer.java │ │ │ │ ├── SentinelDefaultTokenServer.java │ │ │ │ ├── ServerConstants.java │ │ │ │ ├── TokenServiceProvider.java │ │ │ │ ├── codec/ │ │ │ │ │ ├── DefaultRequestEntityDecoder.java │ │ │ │ │ ├── DefaultResponseEntityWriter.java │ │ │ │ │ ├── ServerEntityCodecProvider.java │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── FlowRequestDataDecoder.java │ │ │ │ │ │ ├── FlowResponseDataWriter.java │ │ │ │ │ │ ├── ParamFlowRequestDataDecoder.java │ │ │ │ │ │ ├── PingRequestDataDecoder.java │ │ │ │ │ │ └── PingResponseDataWriter.java │ │ │ │ │ ├── netty/ │ │ │ │ │ │ ├── NettyRequestDecoder.java │ │ │ │ │ │ └── NettyResponseEncoder.java │ │ │ │ │ └── registry/ │ │ │ │ │ ├── RequestDataDecodeRegistry.java │ │ │ │ │ └── ResponseDataWriterRegistry.java │ │ │ │ ├── command/ │ │ │ │ │ └── handler/ │ │ │ │ │ ├── FetchClusterFlowRulesCommandHandler.java │ │ │ │ │ ├── FetchClusterMetricCommandHandler.java │ │ │ │ │ ├── FetchClusterParamFlowRulesCommandHandler.java │ │ │ │ │ ├── FetchClusterServerConfigHandler.java │ │ │ │ │ ├── FetchClusterServerInfoCommandHandler.java │ │ │ │ │ ├── ModifyClusterFlowRulesCommandHandler.java │ │ │ │ │ ├── ModifyClusterParamFlowRulesCommandHandler.java │ │ │ │ │ ├── ModifyClusterServerFlowConfigHandler.java │ │ │ │ │ ├── ModifyClusterServerTransportConfigHandler.java │ │ │ │ │ └── ModifyServerNamespaceSetHandler.java │ │ │ │ ├── config/ │ │ │ │ │ ├── ClusterServerConfigManager.java │ │ │ │ │ ├── ServerFlowConfig.java │ │ │ │ │ ├── ServerTransportConfig.java │ │ │ │ │ └── ServerTransportConfigObserver.java │ │ │ │ ├── connection/ │ │ │ │ │ ├── Connection.java │ │ │ │ │ ├── ConnectionDescriptor.java │ │ │ │ │ ├── ConnectionGroup.java │ │ │ │ │ ├── ConnectionManager.java │ │ │ │ │ ├── ConnectionPool.java │ │ │ │ │ ├── NettyConnection.java │ │ │ │ │ └── ScanIdleConnectionTask.java │ │ │ │ ├── handler/ │ │ │ │ │ └── TokenServerHandler.java │ │ │ │ ├── init/ │ │ │ │ │ └── DefaultClusterServerInitFunc.java │ │ │ │ ├── log/ │ │ │ │ │ └── ClusterServerStatLogUtil.java │ │ │ │ ├── processor/ │ │ │ │ │ ├── FlowRequestProcessor.java │ │ │ │ │ ├── ParamFlowRequestProcessor.java │ │ │ │ │ ├── RequestProcessor.java │ │ │ │ │ └── RequestProcessorProvider.java │ │ │ │ └── util/ │ │ │ │ └── ClusterRuleUtil.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.cluster.TokenService │ │ │ ├── com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder │ │ │ ├── com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter │ │ │ ├── com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer │ │ │ ├── com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor │ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── cluster/ │ │ ├── AbstractTimeBasedTest.java │ │ ├── ClusterFlowTestUtil.java │ │ ├── flow/ │ │ │ ├── ClusterFlowCheckerTest.java │ │ │ ├── ConcurrentClusterFlowCheckerTest.java │ │ │ └── statistic/ │ │ │ ├── concurrent/ │ │ │ │ ├── CurrentConcurrencyManagerTest.java │ │ │ │ └── TokenCacheNodeManagerTest.java │ │ │ ├── limit/ │ │ │ │ ├── GlobalRequestLimiterTest.java │ │ │ │ └── RequestLimiterTest.java │ │ │ └── metric/ │ │ │ ├── ClusterMetricTest.java │ │ │ └── ClusterParamMetricTest.java │ │ └── server/ │ │ ├── AbstractTimeBasedTest.java │ │ ├── codec/ │ │ │ └── data/ │ │ │ └── PingResponseDataWriterTest.java │ │ ├── config/ │ │ │ └── ClusterServerConfigManagerTest.java │ │ └── connection/ │ │ ├── ConnectionGroupTest.java │ │ └── ConnectionManagerTest.java │ └── sentinel-cluster-server-envoy-rls/ │ ├── Dockerfile │ ├── README.md │ ├── pom.xml │ ├── sample/ │ │ └── k8s/ │ │ ├── README.md │ │ ├── envoy-legacy-v2-api.yml │ │ ├── envoy-v3-api.yml │ │ └── sentinel-rls.yml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── cluster/ │ │ │ └── server/ │ │ │ └── envoy/ │ │ │ └── rls/ │ │ │ ├── SentinelEnvoyRlsConstants.java │ │ │ ├── SentinelEnvoyRlsServer.java │ │ │ ├── SentinelEnvoyRlsServiceImpl.java │ │ │ ├── SentinelRlsGrpcServer.java │ │ │ ├── datasource/ │ │ │ │ └── EnvoyRlsRuleDataSourceService.java │ │ │ ├── flow/ │ │ │ │ └── SimpleClusterFlowChecker.java │ │ │ ├── log/ │ │ │ │ └── RlsAccessLogger.java │ │ │ ├── rule/ │ │ │ │ ├── EnvoyRlsRule.java │ │ │ │ ├── EnvoyRlsRuleManager.java │ │ │ │ └── EnvoySentinelRuleConverter.java │ │ │ └── service/ │ │ │ └── v3/ │ │ │ └── SentinelEnvoyRlsServiceImpl.java │ │ └── proto/ │ │ ├── envoy/ │ │ │ ├── api/ │ │ │ │ └── v2/ │ │ │ │ ├── core/ │ │ │ │ │ └── base.proto │ │ │ │ └── ratelimit/ │ │ │ │ └── ratelimit.proto │ │ │ ├── config/ │ │ │ │ └── core/ │ │ │ │ └── v3/ │ │ │ │ └── base.proto │ │ │ ├── extensions/ │ │ │ │ └── common/ │ │ │ │ └── ratelimit/ │ │ │ │ └── v3/ │ │ │ │ └── ratelimit.proto │ │ │ ├── service/ │ │ │ │ └── ratelimit/ │ │ │ │ ├── v2/ │ │ │ │ │ └── rls.proto │ │ │ │ └── v3/ │ │ │ │ └── rls.proto │ │ │ └── type/ │ │ │ └── v3/ │ │ │ └── ratelimit_unit.proto │ │ ├── udpa/ │ │ │ └── annotations/ │ │ │ ├── BUILD │ │ │ ├── migrate.proto │ │ │ ├── security.proto │ │ │ ├── sensitive.proto │ │ │ ├── status.proto │ │ │ └── versioning.proto │ │ └── validate/ │ │ └── validate.proto │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── cluster/ │ └── server/ │ └── envoy/ │ └── rls/ │ ├── SentinelEnvoyRlsServiceImplTest.java │ ├── rule/ │ │ └── EnvoySentinelRuleConverterTest.java │ └── service/ │ └── v3/ │ └── SentinelEnvoyRlsServiceImplTest.java ├── sentinel-core/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ ├── AsyncEntry.java │ │ │ ├── Constants.java │ │ │ ├── CtEntry.java │ │ │ ├── CtSph.java │ │ │ ├── Entry.java │ │ │ ├── EntryType.java │ │ │ ├── Env.java │ │ │ ├── ErrorEntryFreeException.java │ │ │ ├── ResourceTypeConstants.java │ │ │ ├── Sph.java │ │ │ ├── SphO.java │ │ │ ├── SphResourceTypeSupport.java │ │ │ ├── SphU.java │ │ │ ├── Tracer.java │ │ │ ├── annotation/ │ │ │ │ └── SentinelResource.java │ │ │ ├── cluster/ │ │ │ │ ├── ClusterStateManager.java │ │ │ │ ├── TokenResult.java │ │ │ │ ├── TokenResultStatus.java │ │ │ │ ├── TokenServerDescriptor.java │ │ │ │ ├── TokenService.java │ │ │ │ ├── client/ │ │ │ │ │ ├── ClusterTokenClient.java │ │ │ │ │ └── TokenClientProvider.java │ │ │ │ ├── log/ │ │ │ │ │ ├── ClusterClientStatLogUtil.java │ │ │ │ │ └── ClusterStatLogUtil.java │ │ │ │ └── server/ │ │ │ │ ├── ClusterTokenServer.java │ │ │ │ ├── EmbeddedClusterTokenServer.java │ │ │ │ └── EmbeddedClusterTokenServerProvider.java │ │ │ ├── concurrent/ │ │ │ │ └── NamedThreadFactory.java │ │ │ ├── config/ │ │ │ │ ├── SentinelConfig.java │ │ │ │ └── SentinelConfigLoader.java │ │ │ ├── context/ │ │ │ │ ├── Context.java │ │ │ │ ├── ContextNameDefineException.java │ │ │ │ ├── ContextUtil.java │ │ │ │ └── NullContext.java │ │ │ ├── eagleeye/ │ │ │ │ ├── BaseLoggerBuilder.java │ │ │ │ ├── EagleEye.java │ │ │ │ ├── EagleEyeAppender.java │ │ │ │ ├── EagleEyeCoreUtils.java │ │ │ │ ├── EagleEyeLogDaemon.java │ │ │ │ ├── EagleEyeRollingFileAppender.java │ │ │ │ ├── FastDateFormat.java │ │ │ │ ├── StatEntry.java │ │ │ │ ├── StatEntryFunc.java │ │ │ │ ├── StatLogController.java │ │ │ │ ├── StatLogger.java │ │ │ │ ├── StatLoggerBuilder.java │ │ │ │ ├── StatRollingData.java │ │ │ │ ├── SyncAppender.java │ │ │ │ └── TokenBucket.java │ │ │ ├── init/ │ │ │ │ ├── InitExecutor.java │ │ │ │ ├── InitFunc.java │ │ │ │ └── InitOrder.java │ │ │ ├── log/ │ │ │ │ ├── LogBase.java │ │ │ │ ├── LogConfigLoader.java │ │ │ │ ├── LogTarget.java │ │ │ │ ├── Logger.java │ │ │ │ ├── LoggerSpiProvider.java │ │ │ │ ├── RecordLog.java │ │ │ │ └── jul/ │ │ │ │ ├── BaseJulLogger.java │ │ │ │ ├── ConsoleHandler.java │ │ │ │ ├── CspFormatter.java │ │ │ │ ├── DateFileLogHandler.java │ │ │ │ ├── FormattingTuple.java │ │ │ │ ├── JavaLoggingAdapter.java │ │ │ │ ├── Level.java │ │ │ │ └── MessageFormatter.java │ │ │ ├── metric/ │ │ │ │ └── extension/ │ │ │ │ ├── AdvancedMetricExtension.java │ │ │ │ ├── MetricCallbackInit.java │ │ │ │ ├── MetricExtension.java │ │ │ │ ├── MetricExtensionProvider.java │ │ │ │ └── callback/ │ │ │ │ ├── MetricEntryCallback.java │ │ │ │ └── MetricExitCallback.java │ │ │ ├── node/ │ │ │ │ ├── ClusterNode.java │ │ │ │ ├── DefaultNode.java │ │ │ │ ├── EntranceNode.java │ │ │ │ ├── IntervalProperty.java │ │ │ │ ├── Node.java │ │ │ │ ├── NodeBuilder.java │ │ │ │ ├── OccupySupport.java │ │ │ │ ├── OccupyTimeoutProperty.java │ │ │ │ ├── SampleCountProperty.java │ │ │ │ ├── StatisticNode.java │ │ │ │ └── metric/ │ │ │ │ ├── MetricNode.java │ │ │ │ ├── MetricSearcher.java │ │ │ │ ├── MetricTimerListener.java │ │ │ │ ├── MetricWriter.java │ │ │ │ └── MetricsReader.java │ │ │ ├── property/ │ │ │ │ ├── DynamicSentinelProperty.java │ │ │ │ ├── NoOpSentinelProperty.java │ │ │ │ ├── PropertyListener.java │ │ │ │ ├── SentinelProperty.java │ │ │ │ └── SimplePropertyListener.java │ │ │ ├── slotchain/ │ │ │ │ ├── AbstractLinkedProcessorSlot.java │ │ │ │ ├── DefaultProcessorSlotChain.java │ │ │ │ ├── MethodResourceWrapper.java │ │ │ │ ├── ProcessorSlot.java │ │ │ │ ├── ProcessorSlotChain.java │ │ │ │ ├── ProcessorSlotEntryCallback.java │ │ │ │ ├── ProcessorSlotExitCallback.java │ │ │ │ ├── ResourceWrapper.java │ │ │ │ ├── SlotChainBuilder.java │ │ │ │ ├── SlotChainProvider.java │ │ │ │ └── StringResourceWrapper.java │ │ │ ├── slots/ │ │ │ │ ├── DefaultSlotChainBuilder.java │ │ │ │ ├── block/ │ │ │ │ │ ├── AbstractRule.java │ │ │ │ │ ├── BlockException.java │ │ │ │ │ ├── ClusterRuleConstant.java │ │ │ │ │ ├── Rule.java │ │ │ │ │ ├── RuleConstant.java │ │ │ │ │ ├── RuleManager.java │ │ │ │ │ ├── SentinelRpcException.java │ │ │ │ │ ├── authority/ │ │ │ │ │ │ ├── AuthorityException.java │ │ │ │ │ │ ├── AuthorityRule.java │ │ │ │ │ │ ├── AuthorityRuleChecker.java │ │ │ │ │ │ ├── AuthorityRuleManager.java │ │ │ │ │ │ └── AuthoritySlot.java │ │ │ │ │ ├── degrade/ │ │ │ │ │ │ ├── DefaultCircuitBreakerRuleManager.java │ │ │ │ │ │ ├── DefaultCircuitBreakerSlot.java │ │ │ │ │ │ ├── DegradeException.java │ │ │ │ │ │ ├── DegradeRule.java │ │ │ │ │ │ ├── DegradeRuleManager.java │ │ │ │ │ │ ├── DegradeSlot.java │ │ │ │ │ │ └── circuitbreaker/ │ │ │ │ │ │ ├── AbstractCircuitBreaker.java │ │ │ │ │ │ ├── CircuitBreaker.java │ │ │ │ │ │ ├── CircuitBreakerStateChangeObserver.java │ │ │ │ │ │ ├── CircuitBreakerStrategy.java │ │ │ │ │ │ ├── EventObserverRegistry.java │ │ │ │ │ │ ├── ExceptionCircuitBreaker.java │ │ │ │ │ │ └── ResponseTimeCircuitBreaker.java │ │ │ │ │ └── flow/ │ │ │ │ │ ├── ClusterFlowConfig.java │ │ │ │ │ ├── ColdFactorProperty.java │ │ │ │ │ ├── FlowException.java │ │ │ │ │ ├── FlowRule.java │ │ │ │ │ ├── FlowRuleChecker.java │ │ │ │ │ ├── FlowRuleComparator.java │ │ │ │ │ ├── FlowRuleManager.java │ │ │ │ │ ├── FlowRuleUtil.java │ │ │ │ │ ├── FlowSlot.java │ │ │ │ │ ├── PriorityWaitException.java │ │ │ │ │ ├── TrafficShapingController.java │ │ │ │ │ ├── controller/ │ │ │ │ │ │ ├── DefaultController.java │ │ │ │ │ │ ├── ThrottlingController.java │ │ │ │ │ │ ├── WarmUpController.java │ │ │ │ │ │ └── WarmUpRateLimiterController.java │ │ │ │ │ └── tokenbucket/ │ │ │ │ │ ├── AbstractTokenBucket.java │ │ │ │ │ ├── DefaultTokenBucket.java │ │ │ │ │ ├── StrictTokenBucket.java │ │ │ │ │ └── TokenBucket.java │ │ │ │ ├── clusterbuilder/ │ │ │ │ │ └── ClusterBuilderSlot.java │ │ │ │ ├── logger/ │ │ │ │ │ ├── EagleEyeLogUtil.java │ │ │ │ │ └── LogSlot.java │ │ │ │ ├── nodeselector/ │ │ │ │ │ └── NodeSelectorSlot.java │ │ │ │ ├── statistic/ │ │ │ │ │ ├── MetricEvent.java │ │ │ │ │ ├── StatisticSlot.java │ │ │ │ │ ├── StatisticSlotCallbackRegistry.java │ │ │ │ │ ├── base/ │ │ │ │ │ │ ├── LeapArray.java │ │ │ │ │ │ ├── UnaryLeapArray.java │ │ │ │ │ │ └── WindowWrap.java │ │ │ │ │ ├── data/ │ │ │ │ │ │ └── MetricBucket.java │ │ │ │ │ └── metric/ │ │ │ │ │ ├── ArrayMetric.java │ │ │ │ │ ├── BucketLeapArray.java │ │ │ │ │ ├── DebugSupport.java │ │ │ │ │ ├── Metric.java │ │ │ │ │ └── occupy/ │ │ │ │ │ ├── FutureBucketLeapArray.java │ │ │ │ │ └── OccupiableBucketLeapArray.java │ │ │ │ └── system/ │ │ │ │ ├── SystemBlockException.java │ │ │ │ ├── SystemRule.java │ │ │ │ ├── SystemRuleManager.java │ │ │ │ ├── SystemSlot.java │ │ │ │ └── SystemStatusListener.java │ │ │ ├── spi/ │ │ │ │ ├── Spi.java │ │ │ │ ├── SpiLoader.java │ │ │ │ └── SpiLoaderException.java │ │ │ └── util/ │ │ │ ├── AppNameUtil.java │ │ │ ├── AssertUtil.java │ │ │ ├── ConfigUtil.java │ │ │ ├── HostNameUtil.java │ │ │ ├── IdUtil.java │ │ │ ├── MethodUtil.java │ │ │ ├── PidUtil.java │ │ │ ├── StringUtil.java │ │ │ ├── TimeUtil.java │ │ │ ├── VersionUtil.java │ │ │ └── function/ │ │ │ ├── BiConsumer.java │ │ │ ├── Consumer.java │ │ │ ├── Function.java │ │ │ ├── Predicate.java │ │ │ ├── Supplier.java │ │ │ └── Tuple2.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ ├── com.alibaba.csp.sentinel.init.InitFunc │ │ ├── com.alibaba.csp.sentinel.slotchain.ProcessorSlot │ │ └── com.alibaba.csp.sentinel.slotchain.SlotChainBuilder │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ ├── AsyncEntryIntegrationTest.java │ │ ├── AsyncEntryTest.java │ │ ├── ConfigPropertyHelper.java │ │ ├── CtEntryTest.java │ │ ├── CtSphTest.java │ │ ├── EntryTest.java │ │ ├── RecordLogTest.java │ │ ├── SphOTest.java │ │ ├── SphUTest.java │ │ ├── TracerTest.java │ │ ├── config/ │ │ │ └── SentinelConfigTest.java │ │ ├── context/ │ │ │ ├── ContextTest.java │ │ │ └── ContextTestUtil.java │ │ ├── eagleeye/ │ │ │ ├── EagleEyeCoreUtilsTest.java │ │ │ └── TokenBucketTest.java │ │ ├── log/ │ │ │ ├── LogBaseTest.java │ │ │ └── jul/ │ │ │ └── ConsoleHandlerTest.java │ │ ├── metric/ │ │ │ └── extension/ │ │ │ └── callback/ │ │ │ ├── FakeAdvancedMetricExtension.java │ │ │ ├── FakeMetricExtension.java │ │ │ ├── MetricEntryCallbackTest.java │ │ │ └── MetricExitCallbackTest.java │ │ ├── node/ │ │ │ ├── ClusterNodeTest.java │ │ │ ├── StatisticNodeTest.java │ │ │ └── metric/ │ │ │ ├── MetricNodeTest.java │ │ │ └── MetricWriterTest.java │ │ ├── slots/ │ │ │ ├── DefaultSlotChainBuilderTest.java │ │ │ ├── block/ │ │ │ │ ├── RuleManagerTest.java │ │ │ │ ├── authority/ │ │ │ │ │ ├── AuthorityPartialIntegrationTest.java │ │ │ │ │ ├── AuthorityRuleCheckerTest.java │ │ │ │ │ ├── AuthorityRuleManagerTest.java │ │ │ │ │ └── AuthoritySlotTest.java │ │ │ │ ├── degrade/ │ │ │ │ │ ├── CircuitBreakingIntegrationTest.java │ │ │ │ │ ├── DefaultCircuitBreakerRuleManagerTest.java │ │ │ │ │ ├── DefaultCircuitBreakerSlotTest.java │ │ │ │ │ ├── DegradePartialIntegrationTest.java │ │ │ │ │ ├── DegradeRuleManagerTest.java │ │ │ │ │ ├── DegradeRuleTest.java │ │ │ │ │ └── circuitbreaker/ │ │ │ │ │ ├── ExceptionCircuitBreakerTest.java │ │ │ │ │ └── ResponseTimeCircuitBreakerTest.java │ │ │ │ ├── flow/ │ │ │ │ │ ├── FlowPartialIntegrationTest.java │ │ │ │ │ ├── FlowRuleCheckerTest.java │ │ │ │ │ ├── FlowRuleComparatorTest.java │ │ │ │ │ ├── FlowRuleManagerTest.java │ │ │ │ │ ├── FlowSlotTest.java │ │ │ │ │ ├── controller/ │ │ │ │ │ │ ├── DefaultControllerTest.java │ │ │ │ │ │ ├── ThrottlingControllerTest.java │ │ │ │ │ │ ├── WarmUpControllerTest.java │ │ │ │ │ │ └── WarmUpRateLimiterControllerTest.java │ │ │ │ │ └── tokenbucket/ │ │ │ │ │ └── TokenBucketTest.java │ │ │ │ └── system/ │ │ │ │ ├── SystemGuardIntegrationTest.java │ │ │ │ └── SystemRuleTest.java │ │ │ ├── clusterbuilder/ │ │ │ │ └── ClusterNodeBuilderTest.java │ │ │ ├── logger/ │ │ │ │ └── EagleEyeLogUtilTest.java │ │ │ ├── nodeselector/ │ │ │ │ └── NodeSelectorTest.java │ │ │ ├── statistic/ │ │ │ │ ├── base/ │ │ │ │ │ └── LeapArrayTest.java │ │ │ │ └── metric/ │ │ │ │ ├── ArrayMetricTest.java │ │ │ │ ├── BucketLeapArrayTest.java │ │ │ │ ├── FutureBucketLeapArrayTest.java │ │ │ │ └── OccupiableBucketLeapArrayTest.java │ │ │ └── system/ │ │ │ └── SystemRuleManagerTest.java │ │ ├── spi/ │ │ │ ├── SpiLoaderTest.java │ │ │ ├── TestFiveProvider.java │ │ │ ├── TestFourProvider.java │ │ │ ├── TestInterface.java │ │ │ ├── TestNoProviderInterface.java │ │ │ ├── TestNoSpiFileInterface.java │ │ │ ├── TestOneProvider.java │ │ │ ├── TestThreeProvider.java │ │ │ └── TestTwoProvider.java │ │ ├── test/ │ │ │ └── AbstractTimeBasedTest.java │ │ └── util/ │ │ ├── ConfigUtilTest.java │ │ ├── IdUtilTest.java │ │ ├── MethodUtilTest.java │ │ ├── StringUtilTest.java │ │ ├── TimeUtilTest.java │ │ ├── VersionUtilTest.java │ │ └── function/ │ │ └── Tuple2Test.java │ └── resources/ │ └── META-INF/ │ └── services/ │ ├── com.alibaba.csp.sentinel.spi.TestInterface │ └── com.alibaba.csp.sentinel.spi.TestNoProviderInterface ├── sentinel-dashboard/ │ ├── Dockerfile │ ├── README.md │ ├── Sentinel_Dashboard_Feature.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── dashboard/ │ │ │ ├── DashboardApplication.java │ │ │ ├── auth/ │ │ │ │ ├── AuthAction.java │ │ │ │ ├── AuthService.java │ │ │ │ ├── AuthorizationInterceptor.java │ │ │ │ ├── DefaultAuthorizationInterceptor.java │ │ │ │ ├── DefaultLoginAuthenticationFilter.java │ │ │ │ ├── FakeAuthServiceImpl.java │ │ │ │ ├── LoginAuthenticationFilter.java │ │ │ │ └── SimpleWebAuthServiceImpl.java │ │ │ ├── client/ │ │ │ │ ├── CommandFailedException.java │ │ │ │ ├── CommandNotFoundException.java │ │ │ │ └── SentinelApiClient.java │ │ │ ├── config/ │ │ │ │ ├── AuthConfiguration.java │ │ │ │ ├── AuthProperties.java │ │ │ │ ├── DashboardConfig.java │ │ │ │ └── WebConfig.java │ │ │ ├── controller/ │ │ │ │ ├── AppController.java │ │ │ │ ├── AuthController.java │ │ │ │ ├── AuthorityRuleController.java │ │ │ │ ├── DegradeController.java │ │ │ │ ├── DemoController.java │ │ │ │ ├── FlowControllerV1.java │ │ │ │ ├── MachineRegistryController.java │ │ │ │ ├── MetricController.java │ │ │ │ ├── ParamFlowRuleController.java │ │ │ │ ├── ResourceController.java │ │ │ │ ├── SystemController.java │ │ │ │ ├── VersionController.java │ │ │ │ ├── cluster/ │ │ │ │ │ ├── ClusterAssignController.java │ │ │ │ │ └── ClusterConfigController.java │ │ │ │ ├── gateway/ │ │ │ │ │ ├── GatewayApiController.java │ │ │ │ │ └── GatewayFlowRuleController.java │ │ │ │ └── v2/ │ │ │ │ └── FlowControllerV2.java │ │ │ ├── datasource/ │ │ │ │ └── entity/ │ │ │ │ ├── ApplicationEntity.java │ │ │ │ ├── MachineEntity.java │ │ │ │ ├── MetricEntity.java │ │ │ │ ├── MetricPositionEntity.java │ │ │ │ ├── SentinelVersion.java │ │ │ │ ├── gateway/ │ │ │ │ │ ├── ApiDefinitionEntity.java │ │ │ │ │ ├── ApiPredicateItemEntity.java │ │ │ │ │ ├── GatewayFlowRuleEntity.java │ │ │ │ │ └── GatewayParamFlowItemEntity.java │ │ │ │ └── rule/ │ │ │ │ ├── AbstractRuleEntity.java │ │ │ │ ├── AuthorityRuleEntity.java │ │ │ │ ├── DegradeRuleEntity.java │ │ │ │ ├── FlowRuleEntity.java │ │ │ │ ├── ParamFlowRuleEntity.java │ │ │ │ ├── RuleEntity.java │ │ │ │ └── SystemRuleEntity.java │ │ │ ├── discovery/ │ │ │ │ ├── AppInfo.java │ │ │ │ ├── AppManagement.java │ │ │ │ ├── MachineDiscovery.java │ │ │ │ ├── MachineInfo.java │ │ │ │ └── SimpleMachineDiscovery.java │ │ │ ├── domain/ │ │ │ │ ├── ResourceTreeNode.java │ │ │ │ ├── Result.java │ │ │ │ ├── cluster/ │ │ │ │ │ ├── ClusterAppAssignResultVO.java │ │ │ │ │ ├── ClusterAppFullAssignRequest.java │ │ │ │ │ ├── ClusterAppSingleServerAssignRequest.java │ │ │ │ │ ├── ClusterClientInfoVO.java │ │ │ │ │ ├── ClusterGroupEntity.java │ │ │ │ │ ├── ClusterStateSingleVO.java │ │ │ │ │ ├── ConnectionDescriptorVO.java │ │ │ │ │ ├── ConnectionGroupVO.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ClusterClientConfig.java │ │ │ │ │ │ ├── ServerFlowConfig.java │ │ │ │ │ │ └── ServerTransportConfig.java │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── ClusterAppAssignMap.java │ │ │ │ │ │ ├── ClusterClientModifyRequest.java │ │ │ │ │ │ ├── ClusterModifyRequest.java │ │ │ │ │ │ └── ClusterServerModifyRequest.java │ │ │ │ │ └── state/ │ │ │ │ │ ├── AppClusterClientStateWrapVO.java │ │ │ │ │ ├── AppClusterServerStateWrapVO.java │ │ │ │ │ ├── ClusterClientStateVO.java │ │ │ │ │ ├── ClusterRequestLimitVO.java │ │ │ │ │ ├── ClusterServerStateVO.java │ │ │ │ │ ├── ClusterStateSimpleEntity.java │ │ │ │ │ ├── ClusterUniversalStatePairVO.java │ │ │ │ │ └── ClusterUniversalStateVO.java │ │ │ │ └── vo/ │ │ │ │ ├── MachineInfoVo.java │ │ │ │ ├── MetricVo.java │ │ │ │ ├── ResourceVo.java │ │ │ │ └── gateway/ │ │ │ │ ├── api/ │ │ │ │ │ ├── AddApiReqVo.java │ │ │ │ │ ├── ApiPredicateItemVo.java │ │ │ │ │ └── UpdateApiReqVo.java │ │ │ │ └── rule/ │ │ │ │ ├── AddFlowRuleReqVo.java │ │ │ │ ├── GatewayParamFlowItemVo.java │ │ │ │ └── UpdateFlowRuleReqVo.java │ │ │ ├── metric/ │ │ │ │ └── MetricFetcher.java │ │ │ ├── repository/ │ │ │ │ ├── gateway/ │ │ │ │ │ ├── InMemApiDefinitionStore.java │ │ │ │ │ └── InMemGatewayFlowRuleStore.java │ │ │ │ ├── metric/ │ │ │ │ │ ├── InMemoryMetricsRepository.java │ │ │ │ │ └── MetricsRepository.java │ │ │ │ └── rule/ │ │ │ │ ├── InMemAuthorityRuleStore.java │ │ │ │ ├── InMemDegradeRuleStore.java │ │ │ │ ├── InMemFlowRuleStore.java │ │ │ │ ├── InMemParamFlowRuleStore.java │ │ │ │ ├── InMemSystemRuleStore.java │ │ │ │ ├── InMemoryRuleRepositoryAdapter.java │ │ │ │ └── RuleRepository.java │ │ │ ├── rule/ │ │ │ │ ├── DynamicRuleProvider.java │ │ │ │ ├── DynamicRulePublisher.java │ │ │ │ ├── FlowRuleApiProvider.java │ │ │ │ └── FlowRuleApiPublisher.java │ │ │ ├── service/ │ │ │ │ ├── ClusterAssignService.java │ │ │ │ ├── ClusterAssignServiceImpl.java │ │ │ │ └── ClusterConfigService.java │ │ │ └── util/ │ │ │ ├── AsyncUtils.java │ │ │ ├── ClusterEntityUtils.java │ │ │ ├── MachineUtils.java │ │ │ └── VersionUtils.java │ │ ├── resources/ │ │ │ └── application.properties │ │ └── webapp/ │ │ └── resources/ │ │ ├── .gitignore │ │ ├── .jshintrc │ │ ├── README.md │ │ ├── README_zh.md │ │ ├── app/ │ │ │ ├── scripts/ │ │ │ │ ├── app.js │ │ │ │ ├── controllers/ │ │ │ │ │ ├── authority.js │ │ │ │ │ ├── cluster_app_assign_manage.js │ │ │ │ │ ├── cluster_app_server_list.js │ │ │ │ │ ├── cluster_app_server_manage.js │ │ │ │ │ ├── cluster_app_server_monitor.js │ │ │ │ │ ├── cluster_app_token_client_list.js │ │ │ │ │ ├── cluster_single.js │ │ │ │ │ ├── degrade.js │ │ │ │ │ ├── flow_v1.js │ │ │ │ │ ├── flow_v2.js │ │ │ │ │ ├── gateway/ │ │ │ │ │ │ ├── api.js │ │ │ │ │ │ ├── flow.js │ │ │ │ │ │ └── identity.js │ │ │ │ │ ├── home.js │ │ │ │ │ ├── identity.js │ │ │ │ │ ├── login.js │ │ │ │ │ ├── machine.js │ │ │ │ │ ├── main.js │ │ │ │ │ ├── metric.js │ │ │ │ │ ├── param_flow.js │ │ │ │ │ └── system.js │ │ │ │ ├── directives/ │ │ │ │ │ ├── header/ │ │ │ │ │ │ ├── header.html │ │ │ │ │ │ └── header.js │ │ │ │ │ └── sidebar/ │ │ │ │ │ ├── sidebar-search/ │ │ │ │ │ │ ├── sidebar-search.html │ │ │ │ │ │ └── sidebar-search.js │ │ │ │ │ ├── sidebar.html │ │ │ │ │ └── sidebar.js │ │ │ │ ├── filters/ │ │ │ │ │ └── filters.js │ │ │ │ ├── libs/ │ │ │ │ │ └── treeTable.js │ │ │ │ └── services/ │ │ │ │ ├── appservice.js │ │ │ │ ├── auth_service.js │ │ │ │ ├── authority_service.js │ │ │ │ ├── cluster_state_service.js │ │ │ │ ├── degrade_service.js │ │ │ │ ├── flow_service_v1.js │ │ │ │ ├── flow_service_v2.js │ │ │ │ ├── gateway/ │ │ │ │ │ ├── api_service.js │ │ │ │ │ └── flow_service.js │ │ │ │ ├── identityservice.js │ │ │ │ ├── machineservice.js │ │ │ │ ├── metricservice.js │ │ │ │ ├── param_flow_service.js │ │ │ │ ├── systemservice.js │ │ │ │ └── version_service.js │ │ │ ├── styles/ │ │ │ │ ├── main.css │ │ │ │ ├── page.css │ │ │ │ └── timeline.css │ │ │ └── views/ │ │ │ ├── authority.html │ │ │ ├── cluster/ │ │ │ │ ├── client.html │ │ │ │ └── server.html │ │ │ ├── cluster_app_assign_manage.html │ │ │ ├── cluster_app_client_list.html │ │ │ ├── cluster_app_server_list.html │ │ │ ├── cluster_app_server_overview.html │ │ │ ├── cluster_single_config.html │ │ │ ├── dashboard/ │ │ │ │ ├── home.html │ │ │ │ └── main.html │ │ │ ├── degrade.html │ │ │ ├── dialog/ │ │ │ │ ├── authority-rule-dialog.html │ │ │ │ ├── cluster/ │ │ │ │ │ ├── cluster-client-config-dialog.html │ │ │ │ │ ├── cluster-server-assign-dialog.html │ │ │ │ │ └── cluster-server-connection-detail-dialog.html │ │ │ │ ├── confirm-dialog.html │ │ │ │ ├── degrade-rule-dialog.html │ │ │ │ ├── flow-rule-dialog.html │ │ │ │ ├── gateway/ │ │ │ │ │ ├── api-dialog.html │ │ │ │ │ └── flow-rule-dialog.html │ │ │ │ ├── param-flow-rule-dialog.html │ │ │ │ └── system-rule-dialog.html │ │ │ ├── flow_v1.html │ │ │ ├── flow_v2.html │ │ │ ├── gateway/ │ │ │ │ ├── api.html │ │ │ │ ├── flow.html │ │ │ │ └── identity.html │ │ │ ├── identity.html │ │ │ ├── login.html │ │ │ ├── machine.html │ │ │ ├── metric.html │ │ │ ├── pagination.tpl.html │ │ │ ├── param_flow.html │ │ │ └── system.html │ │ ├── dist/ │ │ │ ├── css/ │ │ │ │ └── app.css │ │ │ └── js/ │ │ │ ├── app.js │ │ │ └── app.vendor.js │ │ ├── gulpfile.js │ │ ├── index.htm │ │ ├── index_dev.htm │ │ ├── license-stat.csv │ │ └── package.json │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── dashboard/ │ ├── client/ │ │ └── SentinelApiClientTest.java │ ├── config/ │ │ ├── DashboardConfigTest.java │ │ └── NoAuthConfigurationTest.java │ ├── controller/ │ │ └── gateway/ │ │ ├── GatewayApiControllerTest.java │ │ └── GatewayFlowRuleControllerTest.java │ ├── datasource/ │ │ └── entity/ │ │ ├── JsonSerializeTest.java │ │ └── SentinelVersionTest.java │ ├── discovery/ │ │ ├── AppInfoTest.java │ │ └── MachineInfoTest.java │ ├── repository/ │ │ └── metric/ │ │ └── InMemoryMetricsRepositoryTest.java │ ├── rule/ │ │ ├── apollo/ │ │ │ ├── ApolloConfig.java │ │ │ ├── ApolloConfigUtil.java │ │ │ ├── FlowRuleApolloProvider.java │ │ │ └── FlowRuleApolloPublisher.java │ │ ├── nacos/ │ │ │ ├── FlowRuleNacosProvider.java │ │ │ ├── FlowRuleNacosPublisher.java │ │ │ ├── NacosConfig.java │ │ │ └── NacosConfigUtil.java │ │ └── zookeeper/ │ │ ├── FlowRuleZookeeperProvider.java │ │ ├── FlowRuleZookeeperPublisher.java │ │ ├── ZookeeperConfig.java │ │ └── ZookeeperConfigUtil.java │ └── util/ │ └── VersionUtilsTest.java ├── sentinel-demo/ │ ├── README.md │ ├── pom.xml │ ├── sentinel-demo-annotation-cdi-interceptor/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── annotation/ │ │ │ └── cdi/ │ │ │ └── interceptor/ │ │ │ ├── DemoApplication.java │ │ │ ├── ExceptionUtil.java │ │ │ ├── TestService.java │ │ │ └── TestServiceImpl.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── beans.xml │ ├── sentinel-demo-annotation-spring-aop/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── annotation/ │ │ │ └── aop/ │ │ │ ├── DemoApplication.java │ │ │ ├── config/ │ │ │ │ └── AopConfiguration.java │ │ │ ├── controller/ │ │ │ │ └── DemoController.java │ │ │ └── service/ │ │ │ ├── ExceptionUtil.java │ │ │ ├── TestService.java │ │ │ └── TestServiceImpl.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-apache-dubbo/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── apache/ │ │ └── dubbo/ │ │ ├── FooConsumerBootstrap.java │ │ ├── FooConsumerExceptionDegradeBootstrap.java │ │ ├── FooProviderBootstrap.java │ │ ├── FooService.java │ │ ├── consumer/ │ │ │ ├── ConsumerConfiguration.java │ │ │ └── FooServiceConsumer.java │ │ └── provider/ │ │ ├── FooServiceImpl.java │ │ └── ProviderConfiguration.java │ ├── sentinel-demo-apache-httpclient/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── apache/ │ │ │ └── httpclient/ │ │ │ ├── ApacheHttpClientDemoApplication.java │ │ │ └── controller/ │ │ │ └── ApacheHttpClientTestController.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-apollo-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── datasource/ │ │ │ └── apollo/ │ │ │ ├── ApolloDataSourceDemo.java │ │ │ └── FlowQpsRunner.java │ │ └── resources/ │ │ └── log4j2.xml │ ├── sentinel-demo-basic/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ ├── AsyncEntryDemo.java │ │ ├── authority/ │ │ │ └── AuthorityDemo.java │ │ ├── degrade/ │ │ │ ├── ExceptionRatioCircuitBreakerDemo.java │ │ │ └── SlowRatioCircuitBreakerDemo.java │ │ ├── flow/ │ │ │ ├── FlowQpsDemo.java │ │ │ ├── FlowQpsRegexDemo.java │ │ │ ├── FlowThreadDemo.java │ │ │ ├── PaceFlowDemo.java │ │ │ ├── WarmUpFlowDemo.java │ │ │ └── WarmUpRateLimiterFlowDemo.java │ │ └── system/ │ │ └── SystemGuardDemo.java │ ├── sentinel-demo-cluster/ │ │ ├── pom.xml │ │ ├── sentinel-demo-cluster-embedded/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── demo/ │ │ │ │ └── cluster/ │ │ │ │ ├── DemoConstants.java │ │ │ │ ├── app/ │ │ │ │ │ ├── ClusterDemoApplication.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ └── AopConfig.java │ │ │ │ │ ├── controller/ │ │ │ │ │ │ └── ClusterDemoController.java │ │ │ │ │ └── service/ │ │ │ │ │ └── DemoService.java │ │ │ │ ├── entity/ │ │ │ │ │ └── ClusterGroupEntity.java │ │ │ │ └── init/ │ │ │ │ └── DemoClusterInitFunc.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ │ └── sentinel-demo-cluster-server-alone/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── cluster/ │ │ │ ├── ClusterServerDemo.java │ │ │ ├── DemoConstants.java │ │ │ └── init/ │ │ │ └── DemoClusterServerInitFunc.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ ├── sentinel-demo-command-handler/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── commandhandler/ │ │ │ ├── CommandDemo.java │ │ │ ├── EchoCommandHandler.java │ │ │ └── interceptor/ │ │ │ ├── AllCommandHandlerInterceptor.java │ │ │ └── EchoCommandHandlerInterceptor.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ └── com.alibaba.csp.sentinel.command.CommandHandlerInterceptor │ ├── sentinel-demo-dubbo/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── dubbo/ │ │ ├── FooService.java │ │ ├── consumer/ │ │ │ ├── ConsumerConfiguration.java │ │ │ └── FooServiceConsumer.java │ │ ├── demo1/ │ │ │ ├── FooConsumerBootstrap.java │ │ │ ├── FooProviderBootstrap.java │ │ │ ├── FooServiceImpl.java │ │ │ └── ProviderConfiguration.java │ │ └── demo2/ │ │ ├── FooConsumerBootstrap.java │ │ ├── FooProviderBootstrap.java │ │ ├── FooServiceImpl.java │ │ └── ProviderConfiguration.java │ ├── sentinel-demo-dynamic-file-rule/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── file/ │ │ │ └── rule/ │ │ │ ├── FileDataSourceDemo.java │ │ │ ├── FileDataSourceInit.java │ │ │ ├── FlowQpsRunner.java │ │ │ └── JarFileDataSourceDemo.java │ │ └── resources/ │ │ ├── DegradeRule.json │ │ ├── FlowRule.json │ │ └── SystemRule.json │ ├── sentinel-demo-etcd-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── datasource/ │ │ └── etcd/ │ │ ├── EtcdConfigSender.java │ │ └── EtcdDataSourceDemo.java │ ├── sentinel-demo-jax-rs/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── jaxrs/ │ │ │ ├── CustomExceptionMapper.java │ │ │ ├── HelloEntity.java │ │ │ ├── HelloResource.java │ │ │ ├── JaxRsClientDemo.java │ │ │ ├── JaxRsDemoApplication.java │ │ │ └── SentinelJaxRsConfig.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-log-logback/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── demo/ │ │ │ │ └── log/ │ │ │ │ └── logback/ │ │ │ │ ├── CommandCenterLogLoggerImpl.java │ │ │ │ └── RecordLogLoggerImpl.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── com.alibaba.csp.sentinel.log.Logger │ │ │ └── logback.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── log/ │ │ └── logback/ │ │ ├── CommandCenterLogTest.java │ │ └── RecordLogTest.java │ ├── sentinel-demo-motan/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── motan/ │ │ │ ├── SentinelMotanConsumerService.java │ │ │ ├── SentinelMotanProviderService.java │ │ │ └── service/ │ │ │ ├── MotanDemoService.java │ │ │ └── impl/ │ │ │ └── MotanDemoServiceImpl.java │ │ └── resources/ │ │ └── sentinel.properties │ ├── sentinel-demo-nacos-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── datasource/ │ │ └── nacos/ │ │ ├── FlowQpsRunner.java │ │ ├── NacosConfigSender.java │ │ └── NacosDataSourceDemo.java │ ├── sentinel-demo-okhttp/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── okhttp/ │ │ │ ├── OkHttpDemoApplication.java │ │ │ └── controller/ │ │ │ └── OkHttpTestController.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-parameter-flow-control/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── flow/ │ │ └── param/ │ │ ├── ParamFlowQpsDemo.java │ │ └── ParamFlowQpsRunner.java │ ├── sentinel-demo-quarkus/ │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── .mvn/ │ │ │ └── wrapper/ │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ │ ├── README.md │ │ ├── build-native.sh │ │ ├── build.sh │ │ ├── mvnw │ │ ├── mvnw.cmd │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── docker/ │ │ │ │ ├── Dockerfile.jvm │ │ │ │ └── Dockerfile.native │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── demo/ │ │ │ │ └── quarkus/ │ │ │ │ ├── AppLifecycleBean.java │ │ │ │ ├── CustomExceptionMapper.java │ │ │ │ ├── GreetingFallback.java │ │ │ │ ├── GreetingResource.java │ │ │ │ └── GreetingService.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── resources/ │ │ │ │ └── index.html │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── quarkus/ │ │ ├── GreetingResourceTest.java │ │ └── NativeGreetingResourceIT.java │ ├── sentinel-demo-rocketmq/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── rocketmq/ │ │ ├── Constants.java │ │ ├── PullConsumerDemo.java │ │ └── SyncProducer.java │ ├── sentinel-demo-servlet/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── servlet/ │ │ │ ├── config/ │ │ │ │ └── SentinelConfig.java │ │ │ └── controller/ │ │ │ └── DefaultServlet.java │ │ ├── resources/ │ │ │ └── sentinel.properties │ │ └── webapp/ │ │ └── WEB-INF/ │ │ └── web.xml │ ├── sentinel-demo-slot-spi/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── slot/ │ │ │ ├── DemoApplication.java │ │ │ └── DemoSlot.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.slotchain.ProcessorSlot │ ├── sentinel-demo-slotchain-spi/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── slotchain/ │ │ │ ├── DemoDegradeRuleApplication.java │ │ │ ├── DemoFlowRuleApplication.java │ │ │ └── DemoSlotChainBuilder.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.slotchain.SlotChainBuilder │ ├── sentinel-demo-sofa-rpc/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── sofa/ │ │ │ └── rpc/ │ │ │ ├── DemoConsumer.java │ │ │ ├── DemoProvider.java │ │ │ └── service/ │ │ │ ├── DemoService.java │ │ │ └── impl/ │ │ │ └── DemoServiceImpl.java │ │ └── resources/ │ │ ├── log4j.xml │ │ └── sofa-rpc/ │ │ └── rpc-config.json │ ├── sentinel-demo-spring-cloud-gateway/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── spring/ │ │ │ └── sc/ │ │ │ └── gateway/ │ │ │ ├── GatewayConfiguration.java │ │ │ └── GatewayDemoApplication.java │ │ └── resources/ │ │ └── application.yml │ ├── sentinel-demo-spring-webflux/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── spring/ │ │ │ └── webflux/ │ │ │ ├── WebFluxDemoApplication.java │ │ │ ├── config/ │ │ │ │ ├── RedisConfig.java │ │ │ │ └── WebFluxConfig.java │ │ │ ├── controller/ │ │ │ │ ├── BazController.java │ │ │ │ └── FooController.java │ │ │ └── service/ │ │ │ ├── BazService.java │ │ │ └── FooService.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-spring-webmvc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── spring/ │ │ │ └── webmvc/ │ │ │ ├── WebMvcDemoApplication.java │ │ │ ├── config/ │ │ │ │ ├── InterceptorConfig.java │ │ │ │ └── SentinelSpringMvcBlockHandlerConfig.java │ │ │ ├── controller/ │ │ │ │ └── WebMvcTestController.java │ │ │ └── vo/ │ │ │ └── ResultWrapper.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-transport-spring-mvc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── transport/ │ │ │ └── springmvc/ │ │ │ └── TransportSpringMvcDemoApplication.java │ │ └── resources/ │ │ └── application.properties │ ├── sentinel-demo-zookeeper-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── datasource/ │ │ └── zookeeper/ │ │ ├── ZookeeperConfigSender.java │ │ └── ZookeeperDataSourceDemo.java │ ├── sentinel-demo-zuul-gateway/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── demo/ │ │ │ └── zuul/ │ │ │ └── gateway/ │ │ │ ├── GatewayRuleConfig.java │ │ │ ├── ZuulConfig.java │ │ │ └── ZuulGatewayDemoApplication.java │ │ └── resources/ │ │ └── application.yml │ └── sentinel-demo-zuul2-gateway/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── demo/ │ │ └── zuul2/ │ │ └── gateway/ │ │ ├── FiltersRegisteringService.java │ │ ├── GatewayRuleConfig.java │ │ ├── SampleServerStartup.java │ │ ├── ZuulBootstrap.java │ │ ├── ZuulClasspathFiltersModule.java │ │ ├── ZuulSampleModule.java │ │ └── filters/ │ │ ├── NotFoundEndpoint.java │ │ └── Route.java │ └── resources/ │ ├── application.properties │ └── log4j.properties ├── sentinel-extension/ │ ├── README.md │ ├── pom.xml │ ├── sentinel-annotation-aspectj/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── annotation/ │ │ │ └── aspectj/ │ │ │ ├── AbstractSentinelAspectSupport.java │ │ │ ├── MethodWrapper.java │ │ │ ├── ResourceMetadataRegistry.java │ │ │ └── SentinelResourceAspect.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── annotation/ │ │ └── aspectj/ │ │ ├── AbstractSentinelAspectSupportTest.java │ │ ├── MethodWrapperTest.java │ │ ├── ResourceMetadataRegistryTest.java │ │ └── integration/ │ │ ├── SentinelAnnotationIntegrationTest.java │ │ ├── config/ │ │ │ └── AopTestConfig.java │ │ └── service/ │ │ ├── BarService.java │ │ ├── FooService.java │ │ ├── FooUtil.java │ │ └── GlobalFallback.java │ ├── sentinel-annotation-cdi-interceptor/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── annotation/ │ │ │ │ └── cdi/ │ │ │ │ └── interceptor/ │ │ │ │ ├── AbstractSentinelInterceptorSupport.java │ │ │ │ ├── MethodWrapper.java │ │ │ │ ├── ResourceMetadataRegistry.java │ │ │ │ ├── SentinelResourceBinding.java │ │ │ │ └── SentinelResourceInterceptor.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── beans.xml │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── annotation/ │ │ │ └── cdi/ │ │ │ └── interceptor/ │ │ │ ├── AbstractSentinelInterceptorSupportTest.java │ │ │ ├── MethodWrapperTest.java │ │ │ ├── ResourceMetadataRegistryTest.java │ │ │ └── integration/ │ │ │ ├── SentinelAnnotationInterceptorIntegrationTest.java │ │ │ └── service/ │ │ │ ├── FooService.java │ │ │ └── FooUtil.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── beans.xml │ ├── sentinel-datasource-apollo/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── apollo/ │ │ └── ApolloDataSource.java │ ├── sentinel-datasource-consul/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── consul/ │ │ │ └── ConsulDataSource.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── consul/ │ │ └── ConsulDataSourceTest.java │ ├── sentinel-datasource-etcd/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── etcd/ │ │ │ ├── EtcdConfig.java │ │ │ └── EtcdDataSource.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── etcd/ │ │ └── EtcdDataSourceTest.java │ ├── sentinel-datasource-eureka/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── eureka/ │ │ │ └── EurekaDataSource.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── eureka/ │ │ │ ├── EurekaDataSourceTest.java │ │ │ └── SimpleSpringApplication.java │ │ └── resources/ │ │ └── application.yml │ ├── sentinel-datasource-extension/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ ├── AbstractDataSource.java │ │ ├── AutoRefreshDataSource.java │ │ ├── Converter.java │ │ ├── EmptyDataSource.java │ │ ├── FileInJarReadableDataSource.java │ │ ├── FileRefreshableDataSource.java │ │ ├── FileWritableDataSource.java │ │ ├── ReadableDataSource.java │ │ └── WritableDataSource.java │ ├── sentinel-datasource-nacos/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── nacos/ │ │ └── NacosDataSource.java │ ├── sentinel-datasource-redis/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── redis/ │ │ │ ├── RedisDataSource.java │ │ │ └── config/ │ │ │ ├── RedisConnectionConfig.java │ │ │ └── RedisHostAndPort.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── redis/ │ │ ├── ClusterModeRedisDataSourceTest.java │ │ ├── RedisConnectionConfigTest.java │ │ ├── SentinelModeRedisDataSourceTest.java │ │ └── StandaloneRedisDataSourceTest.java │ ├── sentinel-datasource-spring-cloud-config/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── datasource/ │ │ │ │ └── spring/ │ │ │ │ └── cloud/ │ │ │ │ └── config/ │ │ │ │ ├── SentinelRuleLocator.java │ │ │ │ ├── SentinelRuleStorage.java │ │ │ │ ├── SpringCloudConfigDataSource.java │ │ │ │ └── config/ │ │ │ │ └── DataSourceBootstrapConfiguration.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── spring/ │ │ │ └── cloud/ │ │ │ └── config/ │ │ │ ├── SimpleSpringApplication.java │ │ │ ├── client/ │ │ │ │ └── ConfigClient.java │ │ │ ├── server/ │ │ │ │ └── ConfigServer.java │ │ │ └── test/ │ │ │ ├── SentinelRuleLocatorTests.java │ │ │ └── SpringCouldDataSourceTest.java │ │ └── resources/ │ │ ├── bootstrap.yml │ │ ├── config-client-application.properties │ │ └── config-server-application.properties │ ├── sentinel-datasource-zookeeper/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ └── zookeeper/ │ │ │ └── ZookeeperDataSource.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── datasource/ │ │ └── zookeeper/ │ │ └── ZookeeperDataSourceTest.java │ ├── sentinel-metric-exporter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── metric/ │ │ │ │ ├── MetricExporterInit.java │ │ │ │ ├── collector/ │ │ │ │ │ └── MetricCollector.java │ │ │ │ └── exporter/ │ │ │ │ ├── MetricExporter.java │ │ │ │ └── jmx/ │ │ │ │ ├── JMXMetricExporter.java │ │ │ │ ├── MBeanRegistry.java │ │ │ │ ├── MetricBean.java │ │ │ │ ├── MetricBeanWriter.java │ │ │ │ └── MetricMXBean.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cps/ │ │ └── sentinel/ │ │ └── metric/ │ │ └── exporter/ │ │ ├── MBeanRegistryTest.java │ │ └── MetricBeanWriterTest.java │ ├── sentinel-parameter-flow-control/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ ├── command/ │ │ │ │ │ └── handler/ │ │ │ │ │ ├── GetParamFlowRulesCommandHandler.java │ │ │ │ │ └── ModifyParamFlowRulesCommandHandler.java │ │ │ │ ├── init/ │ │ │ │ │ └── ParamFlowStatisticSlotCallbackInit.java │ │ │ │ └── slots/ │ │ │ │ ├── HotParamSlotChainBuilder.java │ │ │ │ ├── block/ │ │ │ │ │ └── flow/ │ │ │ │ │ └── param/ │ │ │ │ │ ├── ParamFlowArgument.java │ │ │ │ │ ├── ParamFlowChecker.java │ │ │ │ │ ├── ParamFlowClusterConfig.java │ │ │ │ │ ├── ParamFlowException.java │ │ │ │ │ ├── ParamFlowItem.java │ │ │ │ │ ├── ParamFlowRule.java │ │ │ │ │ ├── ParamFlowRuleManager.java │ │ │ │ │ ├── ParamFlowRuleUtil.java │ │ │ │ │ ├── ParamFlowSlot.java │ │ │ │ │ ├── ParameterMetric.java │ │ │ │ │ ├── ParameterMetricStorage.java │ │ │ │ │ ├── RollingParamEvent.java │ │ │ │ │ └── TokenUpdateStatus.java │ │ │ │ └── statistic/ │ │ │ │ ├── ParamFlowStatisticEntryCallback.java │ │ │ │ ├── ParamFlowStatisticExitCallback.java │ │ │ │ ├── cache/ │ │ │ │ │ ├── CacheMap.java │ │ │ │ │ └── ConcurrentLinkedHashMapWrapper.java │ │ │ │ └── data/ │ │ │ │ └── ParamMapBucket.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ │ ├── com.alibaba.csp.sentinel.init.InitFunc │ │ │ └── com.alibaba.csp.sentinel.slotchain.ProcessorSlot │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ ├── block/ │ │ │ └── flow/ │ │ │ └── param/ │ │ │ └── AbstractTimeBasedTest.java │ │ └── slots/ │ │ ├── block/ │ │ │ └── flow/ │ │ │ └── param/ │ │ │ ├── ParamFlowCheckerTest.java │ │ │ ├── ParamFlowDefaultCheckerTest.java │ │ │ ├── ParamFlowPartialIntegrationTest.java │ │ │ ├── ParamFlowRuleManagerTest.java │ │ │ ├── ParamFlowRuleUtilTest.java │ │ │ ├── ParamFlowSlotTest.java │ │ │ ├── ParamFlowThrottleRateLimitingCheckerTest.java │ │ │ ├── ParameterMetricStorageTest.java │ │ │ └── ParameterMetricTest.java │ │ └── statistic/ │ │ └── data/ │ │ └── ParamMapBucketTest.java │ └── sentinel-prometheus-metric-exporter/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── metric/ │ │ │ └── prom/ │ │ │ ├── MetricConstants.java │ │ │ ├── PromExporterInit.java │ │ │ ├── collector/ │ │ │ │ └── SentinelCollector.java │ │ │ ├── config/ │ │ │ │ └── PrometheusGlobalConfig.java │ │ │ └── types/ │ │ │ └── GaugeMetricFamily.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── metric/ │ └── prom/ │ ├── collector/ │ │ └── SentinelCollectorTest.java │ └── types/ │ └── GaugeMetricFamilyTest.java ├── sentinel-logging/ │ ├── pom.xml │ └── sentinel-logging-slf4j/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── logging/ │ │ │ └── slf4j/ │ │ │ ├── CommandCenterLogLogger.java │ │ │ └── RecordLogLogger.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.log.Logger │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── csp/ │ └── sentinel/ │ └── logging/ │ └── slf4j/ │ ├── AbstraceSlf4jLogTest.java │ ├── CommandCenterLogTest.java │ └── RecordLogTest.java ├── sentinel-transport/ │ ├── README.md │ ├── pom.xml │ ├── sentinel-transport-common/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ ├── command/ │ │ │ │ │ ├── CommandCenterProvider.java │ │ │ │ │ ├── CommandConstants.java │ │ │ │ │ ├── CommandHandler.java │ │ │ │ │ ├── CommandHandlerInterceptor.java │ │ │ │ │ ├── CommandHandlerProvider.java │ │ │ │ │ ├── CommandRequest.java │ │ │ │ │ ├── CommandRequestExecution.java │ │ │ │ │ ├── CommandResponse.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ └── CommandMapping.java │ │ │ │ │ ├── handler/ │ │ │ │ │ │ ├── ApiCommandHandler.java │ │ │ │ │ │ ├── BasicInfoCommandHandler.java │ │ │ │ │ │ ├── FetchActiveRuleCommandHandler.java │ │ │ │ │ │ ├── FetchClusterNodeByIdCommandHandler.java │ │ │ │ │ │ ├── FetchClusterNodeHumanCommandHandler.java │ │ │ │ │ │ ├── FetchJsonTreeCommandHandler.java │ │ │ │ │ │ ├── FetchOriginCommandHandler.java │ │ │ │ │ │ ├── FetchSimpleClusterNodeCommandHandler.java │ │ │ │ │ │ ├── FetchSystemStatusCommandHandler.java │ │ │ │ │ │ ├── FetchTreeCommandHandler.java │ │ │ │ │ │ ├── InterceptingCommandHandler.java │ │ │ │ │ │ ├── ModifyRulesCommandHandler.java │ │ │ │ │ │ ├── OnOffGetCommandHandler.java │ │ │ │ │ │ ├── OnOffSetCommandHandler.java │ │ │ │ │ │ ├── SendMetricCommandHandler.java │ │ │ │ │ │ ├── VersionCommandHandler.java │ │ │ │ │ │ └── cluster/ │ │ │ │ │ │ ├── FetchClusterModeCommandHandler.java │ │ │ │ │ │ └── ModifyClusterModeCommandHandler.java │ │ │ │ │ └── vo/ │ │ │ │ │ └── NodeVo.java │ │ │ │ ├── heartbeat/ │ │ │ │ │ └── HeartbeatSenderProvider.java │ │ │ │ └── transport/ │ │ │ │ ├── CommandCenter.java │ │ │ │ ├── HeartbeatSender.java │ │ │ │ ├── client/ │ │ │ │ │ └── CommandClient.java │ │ │ │ ├── config/ │ │ │ │ │ └── TransportConfig.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── Endpoint.java │ │ │ │ │ └── Protocol.java │ │ │ │ ├── init/ │ │ │ │ │ ├── CommandCenterInitFunc.java │ │ │ │ │ └── HeartbeatSenderInitFunc.java │ │ │ │ ├── log/ │ │ │ │ │ └── CommandCenterLog.java │ │ │ │ ├── ssl/ │ │ │ │ │ └── SslFactory.java │ │ │ │ └── util/ │ │ │ │ ├── HttpCommandUtils.java │ │ │ │ └── WritableDataSourceRegistry.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.command.CommandHandler │ │ │ └── com.alibaba.csp.sentinel.init.InitFunc │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── transport/ │ │ │ ├── command/ │ │ │ │ ├── GetRulesCommandHandlerInterceptor.java │ │ │ │ └── InterceptingCommandHandlerTest.java │ │ │ ├── config/ │ │ │ │ └── TransportConfigTest.java │ │ │ ├── endpoint/ │ │ │ │ └── EndpointTest.java │ │ │ └── init/ │ │ │ └── HeartbeatSenderInitFuncTest.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.command.CommandHandlerInterceptor │ ├── sentinel-transport-netty-http/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── transport/ │ │ │ │ ├── command/ │ │ │ │ │ ├── NettyHttpCommandCenter.java │ │ │ │ │ ├── codec/ │ │ │ │ │ │ ├── CodecRegistry.java │ │ │ │ │ │ ├── Decoder.java │ │ │ │ │ │ ├── DefaultCodecs.java │ │ │ │ │ │ ├── Encoder.java │ │ │ │ │ │ ├── StringDecoder.java │ │ │ │ │ │ └── StringEncoder.java │ │ │ │ │ └── netty/ │ │ │ │ │ ├── HttpServer.java │ │ │ │ │ ├── HttpServerHandler.java │ │ │ │ │ └── HttpServerInitializer.java │ │ │ │ └── heartbeat/ │ │ │ │ ├── HttpHeartbeatSender.java │ │ │ │ └── client/ │ │ │ │ └── HttpClientsFactory.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.transport.CommandCenter │ │ │ └── com.alibaba.csp.sentinel.transport.HeartbeatSender │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── csp/ │ │ │ └── sentinel/ │ │ │ └── transport/ │ │ │ └── command/ │ │ │ ├── handler/ │ │ │ │ └── MultipleSlashNameCommandTestHandler.java │ │ │ └── netty/ │ │ │ ├── HttpServerHandlerTest.java │ │ │ ├── HttpServerInitializerTest.java │ │ │ └── HttpServerTest.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.alibaba.csp.sentinel.command.CommandHandler │ ├── sentinel-transport-simple-http/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── csp/ │ │ │ │ └── sentinel/ │ │ │ │ └── transport/ │ │ │ │ ├── command/ │ │ │ │ │ ├── SimpleHttpCommandCenter.java │ │ │ │ │ ├── exception/ │ │ │ │ │ │ └── RequestException.java │ │ │ │ │ └── http/ │ │ │ │ │ ├── HttpEventTask.java │ │ │ │ │ └── StatusCode.java │ │ │ │ └── heartbeat/ │ │ │ │ ├── HeartbeatMessage.java │ │ │ │ ├── SimpleHttpHeartbeatSender.java │ │ │ │ └── client/ │ │ │ │ ├── SimpleHttpClient.java │ │ │ │ ├── SimpleHttpRequest.java │ │ │ │ ├── SimpleHttpResponse.java │ │ │ │ ├── SimpleHttpResponseParser.java │ │ │ │ └── SocketFactory.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ ├── com.alibaba.csp.sentinel.transport.CommandCenter │ │ │ └── com.alibaba.csp.sentinel.transport.HeartbeatSender │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── transport/ │ │ └── command/ │ │ └── http/ │ │ ├── CommandCenterTest.java │ │ └── HttpEventTaskTest.java │ └── sentinel-transport-spring-mvc/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── csp/ │ │ └── sentinel/ │ │ └── transport/ │ │ ├── command/ │ │ │ ├── SentinelApiHandler.java │ │ │ ├── SentinelApiHandlerAdapter.java │ │ │ ├── SentinelApiHandlerMapping.java │ │ │ ├── SpringMvcHttpCommandCenter.java │ │ │ └── http/ │ │ │ └── StatusCode.java │ │ └── heartbeat/ │ │ ├── SpringMvcHttpHeartbeatSender.java │ │ └── client/ │ │ └── HttpClientsFactory.java │ └── resources/ │ └── META-INF/ │ └── services/ │ ├── com.alibaba.csp.sentinel.transport.CommandCenter │ └── com.alibaba.csp.sentinel.transport.HeartbeatSender └── toolchains-example.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codecov.yml ================================================ ignore: - "sentinel-demo/.*" - "sentinel-dashboard/.*" - "sentinel-benchmark/.*" - "sentinel-transport/.*" - "sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/*" comment: # this is a top-level key layout: " condensed_header, diff, condensed_files, components, condensed_footer" behavior: default require_changes: false # if true: only post the comment if coverage changes require_base: false # [true :: must have a base report to post] require_head: true # [true :: must have a head report to post] hide_project_coverage: false # [true :: only show coverage on the git diff] component_management: individual_components: - component_id: sentinel-adapter # this is an identifier that should not be changed name: sentinel-adapter # this is a display name, and can be changed freely paths: - sentinel-adapter - component_id: sentinel-cluster name: sentinel-cluster paths: - sentinel-cluster - component_id: sentinel-core name: sentinel-core paths: - sentinel-core - component_id: sentinel-extension name: sentinel-extension paths: - sentinel-extension - component_id: sentinel-logging name: sentinel-logging paths: - sentinel-logging coverage: status: project: default: target: auto threshold: 0.5% base: auto patch: default: target: 60% threshold: 1% base: auto ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG] " labels: '' assignees: '' --- ## Issue Description Type: *bug report* ### Describe what happened ### Describe what you expected to happen ### How to reproduce it (as minimally and precisely as possible) 1. 2. 3. ### Tell us your environment ### Anything else we need to know? ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- ## Issue Description Type: *feature request* ### Describe what feature you want ### Describe your initial design (if present) ### Additional context Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ## Issue Description Type: *bug report* or *feature request* ### Describe what happened (or what feature you want) ### Describe what you expected to happen ### How to reproduce it (as minimally and precisely as possible) 1. 2. 3. ### Tell us your environment ### Anything else we need to know? ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Describe what this PR does / why we need it ### Does this pull request fix one issue? ### Describe how you did it ### Describe how to verify it ### Special notes for reviews ================================================ FILE: .github/workflows/ci.yml ================================================ name: Sentinel CI on: push: branches: - '*' pull_request: branches: - master - "1.8" - "2.0" jobs: build: runs-on: ubuntu-latest strategy: matrix: java: [8, 11, 17, 21] steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Java for test uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: 'maven' - name: Setup Java for mvn uses: actions/setup-java@v4 with: java-version: 17 distribution: 'temurin' - name: Maven Test With Spring 6.x run: mvn --batch-mode test -Dsurefire.jdk-toolchain-version=${{ matrix.java }} if: ${{ matrix.java >= 17 }} - name: Maven Test Without Spring 6.x run: mvn --batch-mode test -Dsurefire.jdk-toolchain-version=${{ matrix.java }} -Dskip.spring.v6x.test=true if: ${{ matrix.java < 17 }} - name: Build with Maven run: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -DminimumPriority=1 - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: - master - "1.8" - "2.0" pull_request: branches: - master - "1.8" - "2.0" schedule: - cron: '00 09 * * 3' jobs: analyze: name: Analyze runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'java' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Setup Java uses: actions/setup-java@v4 with: java-version: 17 distribution: 'temurin' cache: 'maven' # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/document-lint.yml ================================================ name: document-lint on: push: branches: - '*' pull_request: branches: - master - "1.8" - "2.0" jobs: document-lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - run: npm install -g markdownlint-cli - name: use markdownlint-cli to lint markdown file run: | find ./ -name "*.md" | grep -v vendor | grep -v commandline | grep -v .github | grep -v swagger | grep -v api | \ xargs markdownlint --disable MD010 MD013 MD024 MD029 MD033 MD036 -- ================================================ FILE: .gitignore ================================================ # IntelliJ project files .idea/ *.iml out gen # Visual Studio Code .history/ # Maven target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties !/.mvn/wrapper/maven-wrapper.jar # Eclipse .classpath .settings/ .project bin/ # System related *.DS_Store Thumbs.db # flattened pom file .flattened-pom.xml node_modules/ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) [homepage]: https://www.contributor-covenant.org ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Sentinel Welcome to Sentinel! This document is a guideline about how to contribute to Sentinel. If you find something incorrect or missing, please leave comments / suggestions. ## Before you get started ### Code of Conduct Please make sure to read and observe our [Code of Conduct](./CODE_OF_CONDUCT.md). ### Setting up your development environment You should have JDK 17 or later installed in your system. ### How to run test 1. add `~/.m2/toolchains.xml`, to define JDK and path. See [toolcahinas-example.xml](./toolchains-example.xml). 2. With JDK 17 as your default JDK, you could specify the JDK version to run test, i.e. Run test at JDK 8: ```bash mvn test -Dsurefire.jdk-toolchain-version=8 ``` ## Contributing We are always very happy to have contributions, whether for typo fix, bug fix or big new features. Please do not ever hesitate to ask a question or send a pull request. We strongly value documentation and integration with other projects. We are very glad to accept improvements for these aspects. ### GitHub workflow We use the `master` branch as the development branch, which indicates that this is a unstable branch. Here are the workflow for contributors: 1. Fork to your own 2. Clone fork to local repository 3. Create a new branch and work on it 4. Keep your branch in sync 5. Commit your changes (make sure your commit message concise) 6. Push your commits to your forked repository 7. Create a pull request Please follow [the pull request template](./.github/PULL_REQUEST_TEMPLATE.md). Please make sure the PR has a corresponding issue. After creating a PR, one or more reviewers will be assigned to the pull request. The reviewers will review the code. Before merging a PR, squash any fix review feedback, typo, merged, and rebased sorts of commits. The final commit message should be clear and concise. ### Open an issue / PR We use [GitHub Issues](https://github.com/alibaba/Sentinel/issues) and [Pull Requests](https://github.com/alibaba/Sentinel/pulls) for trackers. If you find a typo in document, find a bug in code, or want new features, or want to give suggestions, you can [open an issue on GitHub](https://github.com/alibaba/Sentinel/issues/new) to report it. Please follow the guideline message in the issue template. If you want to contribute, please follow the [contribution workflow](#github-workflow) and create a new pull request. If your PR contains large changes, e.g. component refactor or new components, please write detailed documents about its design and usage. Note that a single PR should not be too large. If heavy changes are required, it's better to separate the changes to a few individual PRs. ### Code review All code should be well reviewed by one or more committers. Some principles: - Readability: Important code should be well-documented. Comply with our code style. - Elegance: New functions, classes or components should be well designed. - Testability: Important code should be well-tested (high unit test coverage). ## Community ### Contact us #### Mailing list If you have any questions or advice, please contact . #### Gitter Our Gitter room: [https://gitter.im/alibaba/Sentinel](https://gitter.im/alibaba/Sentinel). ================================================ FILE: LICENSE ================================================ 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 (i) 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. ================================================ FILE: README.md ================================================ # Sentinel: The Sentinel of Your Microservices Sentinel Logo [![Sentinel CI](https://github.com/alibaba/Sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/alibaba/Sentinel/actions/workflows/ci.yml) [![Codecov](https://codecov.io/gh/alibaba/Sentinel/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/Sentinel) [![Maven Central](https://img.shields.io/maven-central/v/com.alibaba.csp/sentinel-core.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.alibaba.csp%20AND%20a:sentinel-core) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Gitter](https://badges.gitter.im/alibaba/Sentinel.svg)](https://gitter.im/alibaba/Sentinel) [![Leaderboard](https://img.shields.io/badge/Sentinel-Check%20Your%20Contribution-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=sentinel) ## Introduction As distributed systems become increasingly popular, the reliability between services is becoming more important than ever before. Sentinel takes "flow" as breakthrough point, and works on multiple fields including **flow control**, **traffic shaping**, **concurrency limiting**, **circuit breaking** and **system adaptive overload protection**, to guarantee reliability and resilience for microservices. Sentinel has the following features: - **Rich applicable scenarios**: Sentinel has been wildly used in Alibaba, and has covered almost all the core-scenarios in Double-11 (11.11) Shopping Festivals in the past 10 years, such as “Second Kill” which needs to limit burst flow traffic to meet the system capacity, message peak clipping and valley fills, circuit breaking for unreliable downstream services, cluster flow control, etc. - **Real-time monitoring**: Sentinel also provides real-time monitoring ability. You can see the runtime information of a single machine in real-time, and the aggregated runtime info of a cluster with less than 500 nodes. - **Widespread open-source ecosystem**: Sentinel provides out-of-box integrations with commonly-used frameworks and libraries such as Spring Cloud, gRPC, Apache Dubbo and Quarkus. You can easily use Sentinel by simply add the adapter dependency to your services. - **Polyglot support**: Sentinel has provided native support for Java, [Go](https://github.com/alibaba/sentinel-golang), [C++](https://github.com/alibaba/sentinel-cpp) and [Rust](https://github.com/sentinel-group/sentinel-rust). - **Various SPI extensions**: Sentinel provides easy-to-use SPI extension interfaces that allow you to quickly customize your logic, for example, custom rule management, adapting data sources, and so on. Features overview: ![features-of-sentinel](./doc/image/sentinel-features-overview-en.png) The community is also working on **the specification of traffic governance and fault-tolerance**. Please refer to [OpenSergo](https://opensergo.io/) for details. ## Documentation See the [Sentinel Website](https://sentinelguard.io/) for the official website of Sentinel. See the [中文文档](https://sentinelguard.io/zh-cn/docs/introduction.html) for document in Chinese. See the [Wiki](https://github.com/alibaba/Sentinel/wiki) for full documentation, examples, blog posts, operational details and other information. Sentinel provides integration modules for various open-source frameworks (e.g. Spring Cloud, Apache Dubbo, gRPC, Quarkus, Spring WebFlux, Reactor) and service mesh. You can refer to [the document](https://sentinelguard.io/en-us/docs/open-source-framework-integrations.html) for more information. If you are using Sentinel, please [**leave a comment here**](https://github.com/alibaba/Sentinel/issues/18) to tell us your scenario to make Sentinel better. It's also encouraged to add the link of your blog post, tutorial, demo or customized components to [**Awesome Sentinel**](./doc/awesome-sentinel.md). ## Ecosystem Landscape ![ecosystem-landscape](./doc/image/sentinel-opensource-eco-landscape-en.png) ## Quick Start Below is a simple demo that guides new users to use Sentinel in just 3 steps. It also shows how to monitor this demo using the dashboard. ### 1. Add Dependency **Note:** Sentinel requires JDK 1.8 or later. If you're using Maven, just add the following dependency in `pom.xml`. ```xml com.alibaba.csp sentinel-core 1.8.9 ``` If not, you can download JAR in [Maven Center Repository](https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core). ### 2. Define Resource Wrap your code snippet via Sentinel API: `SphU.entry(resourceName)`. In below example, it is `System.out.println("hello world");`: ```java try (Entry entry = SphU.entry("HelloWorld")) { // Your business logic here. System.out.println("hello world"); } catch (BlockException e) { // Handle rejected request. e.printStackTrace(); } // try-with-resources auto exit ``` So far the code modification is done. We've also provided [annotation support module](https://github.com/alibaba/Sentinel/blob/master/sentinel-extension/sentinel-annotation-aspectj/README.md) to define resource easier. ### 3. Define Rules If we want to limit the access times of the resource, we can **set rules to the resource**. The following code defines a rule that limits access to the resource to 20 times per second at the maximum. ```java List rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("HelloWorld"); // set limit qps to 20 rule.setCount(20); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rules.add(rule); FlowRuleManager.loadRules(rules); ``` For more information, please refer to [How To Use](https://sentinelguard.io/en-us/docs/basic-api-resource-rule.html). ### 4. Check the Result After running the demo for a while, you can see the following records in `~/logs/csp/${appName}-metrics.log.{date}` (When using the default `DateFileLogHandler`). ```plaintext |--timestamp-|------date time----|-resource-|p |block|s |e|rt |occupied 1529998904000|2018-06-26 15:41:44|HelloWorld|20|0 |20|0|0 |0 1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728 |0 1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0 |0 1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0 |0 1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0 |0 1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0 |0 p stands for incoming request, block for blocked by rules, s for success handled by Sentinel, e for exception count, rt for average response time (ms), occupied stands for occupiedPassQps since 1.5.0 which enable us booking more than 1 shot when entering. ``` This shows that the demo can print "hello world" 20 times per second. More examples and information can be found in the [How To Use](https://sentinelguard.io/en-us/docs/basic-api-resource-rule.html) section. The working principles of Sentinel can be found in [How it works](https://sentinelguard.io/en-us/docs/basic-implementation.html) section. Samples can be found in the [sentinel-demo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo) module. ### 5. Start Dashboard > Note: Java 8 is required for building or running the dashboard. Sentinel also provides a simple dashboard application, on which you can monitor the clients and configure the rules in real time. ![dashboard](https://user-images.githubusercontent.com/9434884/55449295-84866d80-55fd-11e9-94e5-d3441f4a2b63.png) For details please refer to [Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard). ## Trouble Shooting and Logs Sentinel will generate logs for troubleshooting and real-time monitoring. All the information can be found in [logs](https://sentinelguard.io/en-us/docs/logs.html). ## Bugs and Feedback For bug report, questions and discussions please submit [GitHub Issues](https://github.com/alibaba/sentinel/issues). Contact us via [Gitter](https://gitter.im/alibaba/Sentinel) or [Email](mailto:sentinel@linux.alibaba.com). ## Contributing Contributions are always welcomed! Please refer to [CONTRIBUTING](./CONTRIBUTING.md) for detailed guidelines. You can start with the issues labeled with [`good first issue`](https://github.com/alibaba/Sentinel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). ## Enterprise Service If you need Sentinel enterprise service support (Sentinel 企业版), or purchase cloud product services, you can join the discussion by the DingTalk group (34754806). It can also be directly activated and used through the [microservice engine (MSE 微服务引擎) provided by Alibaba Cloud](https://cn.aliyun.com/product/aliware/mse?spm=sentinel-github.index.0.0.0). ## Credits Thanks [Guava](https://github.com/google/guava), which provides some inspiration on rate limiting. And thanks for all [contributors](https://github.com/alibaba/Sentinel/graphs/contributors) of Sentinel! ## Who is using These are only part of the companies using Sentinel, for reference only. If you are using Sentinel, please [add your company here](https://github.com/alibaba/Sentinel/issues/18) to tell us your scenario to make Sentinel better :) ![Alibaba Group](https://docs.alibabagroup.com/assets2/images/en/global/logo_header.png) ![AntFin](https://user-images.githubusercontent.com/9434884/90598732-30961c00-e226-11ea-8c86-0b1d7f7875c7.png) ![Taiping Renshou](http://www.cntaiping.com/tplresource/cms/www/taiping/img/home_new/tp_logo_img.png) ![拼多多](http://cdn.pinduoduo.com/assets/img/pdd_logo_v3.png) ![爱奇艺](https://user-images.githubusercontent.com/9434884/90598445-a51c8b00-e225-11ea-9327-3543525f3f2a.png) ![Shunfeng Technology](https://user-images.githubusercontent.com/9434884/48463502-2f48eb80-e817-11e8-984f-2f9b1b789e2d.png) ![二维火](https://user-images.githubusercontent.com/9434884/49358468-bc43de00-f70d-11e8-97fe-0bf05865f29f.png) ![Mandao](https://user-images.githubusercontent.com/9434884/48463559-6cad7900-e817-11e8-87e4-42952b074837.png) ![文轩在线](http://static.winxuancdn.com/css/v2/images/logo.png) ![客如云](https://www.keruyun.com/static/krynew/images/logo.png) ![亲宝宝](https://stlib.qbb6.com/wclt/img/home_hd/version1/title_logo.png) ![金汇金融](https://res.jinhui365.com/r/images/logo2.png?v=1.527) ![闪电购](http://cdn.52shangou.com/shandianbang/official-source/3.1.1/build/images/logo.png) ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Reporting a security issue If you think the bug you found is likely to make Sentinel-based applications vulnerable to an attack, please do not use our public issue tracker but report it to [ASRC(Alibaba Security Response Center)](https://security.alibaba.com/). ================================================ FILE: doc/README.md ================================================ # Sentinel related documents - [Awesome Sentinel](./awesome-sentinel.md) ================================================ FILE: doc/awesome-sentinel.md ================================================ # Awesome Sentinel [![Awesome](https://awesome.re/badge-flat.svg)](https://awesome.re) A curated list of awesome things (e.g. samples, third-party extensions, blog posts) for [Sentinel](https://github.com/alibaba/Sentinel). If you want your component to appear here, feel free to submit a pull request to this repository to add it. You can refer to the [awesome contribution guidelines](https://github.com/sentinel-group/sentinel-awesome/blob/master/CONTRIBUTING.md). You can also add to [sentinel-group/sentinel-awesome](https://github.com/sentinel-group/sentinel-awesome). ## Contents - [Presentations](#presentations) - [Tutorials](#tutorialssamples) - [Demos](../demos) - [Extensions / Integrations](#extensions--integrations) - [Blog Posts](#blog-posts) ## Presentations - Sentinel 1.6.0 网关流控新特性介绍-Eric Zhao (Dubbo Tech Day-201905-Beijing): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%201.6.0%20网关流控新特性介绍-Eric%20Zhao-DTED-201905.pdf) - Sentinel 微服务流控降级实践-Eric Zhao (Dubbo Tech Day-201907-Shenzhen): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%20微服务流控降级实践-Eric%20Zhao-DTED-201907.pdf) - Sentinel 1.7.0 新特性展望-Eric Zhao (Dubbo Tech Day-201910-Chengdu): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%201.7.0%20新特性展望-Eric%20Zhao-DTED-201910.pdf) - 《微服务容易挂?Sentinel Go 让微服务稳如磐石》-Eric Zhao (Spring Cloud Alibaba Meetup 1205 杭州站): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/SCA%20Meetup%20Hangzhou-20201205-Sentinel%20Go-Eric%20Zhao.pdf) - Sentinel 2.0 流量治理全面升级-Eric Zhao (2022中间件开发者大会): [PDF](https://github.com/mse-group/Slides/blob/main/中间件开发者大会/10%20-%20赵奕豪(宿何)%20-Sentinel%202.0%20流量治理全面升级.pdf) ## Tutorials/Samples - [Sentinel Guides](https://github.com/sentinel-group/sentinel-guides) ## Polyglot Support - Sentinel Go [Sentinel Go](https://github.com/alibaba/sentinel-golang) - Sentinel C++ [Sentinel C++](https://github.com/alibaba/sentinel-cpp) - Sentinel Rust [Sentinel Rust](https://github.com/sentinel-group/sentinel-rust) ## Extensions / Integrations - [sentinel-support](https://github.com/cdfive/sentinel-support): A support project for convenient Sentinel integration including properties file configuration, ActiveMQ integration and a JdbcDataSource implementation by [cdfive](https://github.com/cdfive) - [Sentinel dashboard multi-data-source adapter](https://github.com/finefuture/sentinel-dashboard-X): Sentinel dashboard multi-data-source adapter has integrated Apollo and Nacos configuration center for bidirectional modification persistence. Implemented by [finefuture](https://github.com/finefuture) - [Sentinel Rule Annotation Support](https://github.com/code1986/sentinel-lib): A third-party library that supports configuring flow rule and degrade rule using annotation. Implemented by [code1986](https://github.com/code1986) - [sentinel-pigeon-adapter](https://github.com/wchswchs/sentinel-pigeon): A RPC framework Pigeon adapter for Sentinel including provider and invoker rate limiting implementation by [wchswchs](https://github.com/wchswchs) ## Blog Posts - [Sentinel 为 Dubbo 服务保驾护航](https://dubbo.apache.org/zh/blog/2018/07/27/sentinel-为-dubbo-服务保驾护航) by [Eric Zhao](https://github.com/sczyh30) - [在生产环境中使用 Sentinel 控制台](https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel) by [Eric Zhao](https://github.com/sczyh30) - [Sentinel 与 Hystrix 的对比](https://sentinelguard.io/zh-cn/blog/sentinel-vs-hystrix.html) by [Eric Zhao](https://github.com/sczyh30) - [Guideline: 从 Hystrix 迁移到 Sentinel](https://sentinelguard.io/zh-cn/blog/guideline-migrate-from-hystrix-to-sentinel.html) by [Eric Zhao](https://github.com/sczyh30) - [Sentinel 控制台监控数据持久化【MySQL】](https://www.cnblogs.com/cdfive2018/p/9838577.html) by [cdfive](https://github.com/cdfive) - [Sentinel 控制台监控数据持久化【InfluxDB】](https://www.cnblogs.com/cdfive2018/p/9914838.html) by [cdfive](https://github.com/cdfive) - [Sentinel 控制台监控数据持久化【Apollo】](https://blog.csdn.net/caodegao/article/details/100009618) by [cookiejoo](https://github.com/cookiejoo) - [Sentinel一体化监控解决方案 CrateDB + Grafana](https://blog.csdn.net/huyong1990/article/details/82392386) by [Young Hu](https://github.com/YoungHu) - Sentinel 源码解析系列 by [houyi](https://github.com/all4you) - [Sentinel 原理-全解析](https://mp.weixin.qq.com/s/7_pCkamNv0269e5l9_Wz7w) - [Sentinel 原理-调用链](https://mp.weixin.qq.com/s/UEzwD22YC6jpp02foNSXnw) - [Sentinel 原理-滑动窗口](https://mp.weixin.qq.com/s/B1_7Kb_CxeKEAv43kdCWOA) - [Sentinel 实战-限流](https://mp.weixin.qq.com/s/rjyU37Dm-sxNln7GUD8tOw) - [Sentinel 实战-控制台](https://mp.weixin.qq.com/s/23EDFHMXLwsDqw-4O5dR5A) - [Sentinel 实战-规则持久化](https://mp.weixin.qq.com/s/twMFiBfRawKLR-1-N-f1yw) - Sentinel 学习笔记 by [ro9er](https://github.com/ro9er) - [Sentinel 学习笔记(1)-- 流量统计代码解析](https://www.jianshu.com/p/7936d7a57924) - [Sentinel 学习笔记(2)-- 流量控制代码分析](https://www.jianshu.com/p/938709e94e43) - [Sentinel 学习笔记(3)-- 上下文统计Node建立分析](https://www.jianshu.com/p/cfdf525248c1) - [大流量下的服务质量治理 Dubbo Sentinel 初涉](https://mp.weixin.qq.com/s/ergr_siI07VwwSRPFgsLvQ) by [RyuGrade](https://github.com/RyuGrade) - Sentinel 深入浅出系列 by [shxz130](https://github.com/shxz130) - [Sentinel 深入浅出之原理篇 SlotChain](https://www.jianshu.com/p/a7a405de3a12) - [Sentinel 深入浅出之原理篇 Context初始化 & Entry初始化](https://www.jianshu.com/p/e39ac47cd893) - [Sentinel 深入浅出之原理篇 NodeSelectorSlot](https://www.jianshu.com/p/9a380ba188ab) - [Sentinel 深入浅出之原理篇 ClusterBuilderSlot](https://www.jianshu.com/p/0b0b5d8888a2) - [Sentinel 深入浅出之原理篇 StatisticSlot&滑动窗口](https://www.jianshu.com/p/9620298fd15a) - [Sentinel 深入浅出之原理篇 SystemSlot](https://www.jianshu.com/p/bfad1b7d0cde) - [Sentinel 深入浅出之原理篇 AuthoritySlot](https://www.jianshu.com/p/c5312c2242b3) - [Sentinel 深入浅出之原理篇 FlowSlot](https://www.jianshu.com/p/53218d0d273e) - [Sentinel 深入浅出之原理篇 DegradeSlot](https://www.jianshu.com/p/e910d4840e4a) - [Alibaba Sentinel RESTful 接口流控处理优化](https://www.jianshu.com/p/96f5980d9798) by [luanlouis](https://github.com/luanlouis) - [Sentinel 控制台前端开发环境搭建](https://www.cnblogs.com/cdfive2018/p/11084001.html) by [cdfive](https://github.com/cdfive) - [Sentinel 如何接入 OpenTracing](https://juejin.im/post/5de32fe46fb9a071a828feeb) by [Zhang Shun](https://github.com/ZShUn) - [阿里 Sentinel 源码解析](https://www.javadoop.com/post/sentinel) by [Javadoop](https://www.javadoop.com) - [阿里巴巴开源限流降级神器 Sentinel 大规模生产级应用实践](https://mp.weixin.qq.com/s/AjHCUmygTr78yo9yMxMEyg) by 步崖 - [Introduction to Alibaba Sentinel](https://www.baeldung.com/java-sentinel-intro) by [Amit Bhave](https://github.com/Amitbhave) - [Sentinel Go 毫秒级统计数据结构揭秘](https://sentinelguard.io/zh-cn/blog/sentinel-go-internal-data-structure.html) by [Binbin Zhang](https://github.com/binbin0325) ================================================ FILE: pom.xml ================================================ 4.0.0 com.alibaba.csp sentinel-parent ${revision} pom ${project.artifactId} The parent project of Sentinel https://github.com/alibaba/Sentinel Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 repo https://github.com/alibaba/Sentinel scm:git:https://github.com/alibaba/Sentinel.git scm:git:https://github.com/alibaba/Sentinel.git The Sentinel Project Contributors sentinel-dev@linux.alibaba.com https://github.com/alibaba/Sentinel Alibaba Group https://github.com/alibaba github https://github.com/alibaba/Sentinel/issues 1.8.9 1.2.83_noneautotype 1.3.2 4.12 4.11.0 1.14.10 3.12.1 3.1.5 UTF-8 1.8 1.8 UTF-8 3.12.0 3.2.5 3.0.1 3.0.1 2.8.2 1.6 0.8.11 3.3.0 3.8 sentinel-core sentinel-extension sentinel-transport sentinel-adapter sentinel-cluster sentinel-logging sentinel-dashboard sentinel-demo sentinel-benchmark com.alibaba.csp sentinel-core ${project.version} com.alibaba.csp sentinel-extension ${project.version} com.alibaba.csp sentinel-annotation-aspectj ${project.version} com.alibaba.csp sentinel-annotation-cdi-interceptor ${project.version} com.alibaba.csp sentinel-parameter-flow-control ${project.version} com.alibaba.csp sentinel-datasource-extension ${project.version} com.alibaba.csp sentinel-datasource-nacos ${project.version} com.alibaba.csp sentinel-datasource-zookeeper ${project.version} com.alibaba.csp sentinel-datasource-apollo ${project.version} com.alibaba.csp sentinel-datasource-etcd ${project.version} com.alibaba.csp sentinel-transport-simple-http ${project.version} com.alibaba.csp sentinel-transport-netty-http ${project.version} com.alibaba.csp sentinel-transport-spring-mvc ${project.version} com.alibaba.csp sentinel-transport-common ${project.version} com.alibaba.csp sentinel-cluster-common-default ${project.version} com.alibaba.csp sentinel-web-adapter-common ${project.version} com.alibaba.csp sentinel-adapter ${project.version} com.alibaba.csp sentinel-metric-exporter ${project.version} com.alibaba fastjson ${fastjson.version} junit junit ${junit.version} test org.mockito mockito-core ${mockito.version} test org.mockito mockito-inline ${mockito.version} test net.bytebuddy byte-buddy ${byte-buddy.version} net.bytebuddy byte-buddy-agent ${byte-buddy.version} org.assertj assertj-core ${assertj.version} test org.awaitility awaitility ${awaitility.version} test org.hamcrest java-hamcrest 2.0.0.0 test org.codehaus.mojo flatten-maven-plugin 1.7.3 true resolveCiFriendliesOnly flatten process-resources flatten flatten.clean clean clean org.apache.maven.plugins maven-pmd-plugin ${maven.pmd.version} ${project.build.sourceEncoding} 1 com/alibaba/csp/sentinel/benchmark **/*_jmhTest.java true 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 verify check com.alibaba.p3c p3c-pmd 1.3.6 org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.version} default-compile 17 base-compile compile module-info.java 8 org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} org.jacoco jacoco-maven-plugin ${maven.jacoco.version} prepare-agent report test report org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} org.apache.maven.plugins maven-jar-plugin ${maven.jar.version} org.apache.maven.plugins maven-gpg-plugin ${maven.gpg.version} central org.apache.maven.plugins maven-source-plugin ${maven.source.version} package jar-no-fork org.apache.maven.plugins maven-javadoc-plugin ${maven.javadoc.version} package jar en_US UTF-8 UTF-8 none org.apache.maven.plugins maven-gpg-plugin ${maven.gpg.version} verify sign org.sonatype.central central-publishing-maven-plugin 0.8.0 true central sentinel-benchmark sentinel-dashboard sentinel-demo sentinel-demo-basic sentinel-demo-dynamic-file-rule sentinel-demo-rocketmq sentinel-demo-dubbo sentinel-demo-nacos-datasource sentinel-demo-zookeeper-datasource sentinel-demo-apollo-datasource sentinel-demo-annotation-spring-aop sentinel-demo-parameter-flow-control sentinel-demo-slot-spi sentinel-demo-slotchain-spi sentinel-demo-cluster sentinel-demo-cluster-embedded sentinel-demo-cluster-server-alone sentinel-demo-command-handler sentinel-demo-spring-webflux sentinel-demo-apache-dubbo sentinel-demo-apache-httpclient sentinel-demo-sofa-rpc sentinel-demo-spring-cloud-gateway sentinel-demo-zuul-gateway sentinel-demo-etcd-datasource sentinel-demo-spring-webmvc sentinel-demo-zuul2-gateway sentinel-demo-log-logback sentinel-demo-okhttp sentinel-demo-jax-rs sentinel-demo-quarkus sentinel-demo-annotation-cdi-interceptor sentinel-demo-motan sentinel-demo-transport-spring-mvc sentinel-demo-servlet central https://central.sonatype.com/repository/maven-snapshots/ central https://central.sonatype.org/service/local/staging/deploy/maven2/ custom-test-runtime-version surefire.jdk-toolchain-version org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} ${surefire.jdk-toolchain-version} ================================================ FILE: sentinel-adapter/pom.xml ================================================ 4.0.0 com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-adapter pom sentinel-adapter The adapters of Sentinel sentinel-web-servlet sentinel-dubbo-adapter sentinel-apache-dubbo-adapter sentinel-apache-dubbo3-adapter sentinel-apache-httpclient-adapter sentinel-sofa-rpc-adapter sentinel-grpc-adapter sentinel-zuul-adapter sentinel-reactor-adapter sentinel-spring-webflux-adapter sentinel-api-gateway-adapter-common sentinel-spring-cloud-gateway-adapter sentinel-spring-cloud-gateway-v6x-adapter sentinel-web-adapter-common sentinel-spring-webmvc-adapter sentinel-spring-webmvc-v6x-adapter sentinel-zuul2-adapter sentinel-okhttp-adapter sentinel-spring-restclient-adapter sentinel-jax-rs-adapter sentinel-quarkus-adapter sentinel-motan-adapter com.alibaba.csp sentinel-core ${project.version} com.alibaba.csp sentinel-extension ${project.version} com.alibaba.csp sentinel-web-servlet ${project.version} com.alibaba.csp sentinel-reactor-adapter ${project.version} com.alibaba.csp sentinel-api-gateway-adapter-common ${project.version} junit junit ${junit.version} test org.mockito mockito-core ${mockito.version} test ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/README.md ================================================ # Sentinel Apache Dubbo Adapter (for 2.7.x+) > Note: 中文文档请见[此处](https://sentinelguard.io/zh-cn/docs/open-source-framework-integrations.html)。 Sentinel Dubbo Adapter provides service consumer filter and provider filter for [Apache Dubbo](https://dubbo.apache.org/en/) services. **Note: This adapter only supports Apache Dubbo 2.7.x and above.** For legacy `com.alibaba:dubbo` 2.6.x, please use `sentinel-dubbo-adapter` module instead. To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-apache-dubbo-adapter x.y.z ``` The Sentinel filters are **enabled by default**. Once you add the dependency, the Dubbo services and methods will become protected resources in Sentinel, which can leverage Sentinel's flow control and guard ability when rules are configured. Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo). If you don't want the filters enabled, you can manually disable them. For example: ```xml ``` For more details of Dubbo filter, see [Dubbo filter documentation](https://cn.dubbo.apache.org/en/overview/mannual/java-sdk/tasks/extensibility/filter/). ## Dubbo resources The resource for Dubbo services has two granularities: service interface and service method. - Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` - Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` ## Flow control based on caller In many circumstances, it's also significant to control traffic flow based on the **caller**. For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider. If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name). Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`), and will bring the caller's name when doing resource protection. If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers. If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. > Note: Earlier Dubbo 2.7.x consumer does not provide its Dubbo application name when doing RPC, > so developers should manually put the application name into *attachment* at consumer side, > then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) > where consumer can carry application name information to provider automatically. > If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, > developers can manually put the application name into attachment with the key `dubboApplication`. > > Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser` > implementation to `DubboAdapterGlobalConfig`. ## Global fallback Sentinel Dubbo Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `DubboFallback` interface and then register to `DubboAdapterGlobalConfig`. If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result. Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services. ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-apache-dubbo-adapter jar 1.8 1.8 2.7.13 com.alibaba.csp sentinel-core org.apache.dubbo dubbo ${apache.dubbo.version} provided junit junit test org.mockito mockito-inline test com.alibaba fastjson test ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. * * @author Zechao Zheng */ public abstract class BaseSentinelDubboFilter implements Filter { /** * Get method name of dubbo rpc * * @param invoker * @param invocation * @return */ abstract String getMethodName(Invoker invoker, Invocation invocation, String prefix); /** * Get interface name of dubbo rpc * * @param invoker * @return */ abstract String getInterfaceName(Invoker invoker, String prefix); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcException; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; /** * Puts current consumer's application name in the attachment of each invocation. * * @author Eric Zhao */ @Activate(group = CONSUMER) public class DubboAppContextFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY); if (application != null) { RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application); } return invoker.invoke(invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * @author Eric Zhao */ public final class DubboUtils { public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication"; public static String getApplication(Invocation invocation, String defaultValue) { if (invocation == null || invocation.getAttachments() == null) { throw new IllegalArgumentException("Bad invocation instance"); } return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); } public static String getMethodResourceName(Invoker invoker, Invocation invocation){ return getMethodResourceName(invoker, invocation, false); } public static String getMethodResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); buf.append(interfaceResource) .append(":") .append(invocation.getMethodName()) .append("("); boolean isFirst = true; for (Class clazz : invocation.getParameterTypes()) { if (!isFirst) { buf.append(","); } buf.append(clazz.getName()); isFirst = false; } buf.append(")"); return buf.toString(); } public static String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { return getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); } } public static String getInterfaceName(Invoker invoker) { return getInterfaceName(invoker, false); } public static String getInterfaceName(Invoker invoker, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); return useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); } public static String getInterfaceName(Invoker invoker, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { return getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); } } private DubboUtils() { } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.support.RpcUtils; import java.util.LinkedList; import java.util.Optional; import java.util.function.BiConsumer; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; /** *

Dubbo service consumer filter for Sentinel. Auto activated by default.

*

* If you want to disable the consumer filter, you can configure: *

 * <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
 * 
* * @author Carpenter Lee * @author Eric Zhao * @author Lin Liang */ @Activate(group = CONSUMER) public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Apache Dubbo consumer filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); if (InvokeMode.SYNC == invokeMode) { return syncInvoke(invoker, invocation); } else { return asyncInvoke(invoker, invocation); } } private Result syncInvoke(Invoker invoker, Invocation invocation) { Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } } } private Result asyncInvoke(Invoker invoker, Invocation invocation) { LinkedList queue = new LinkedList<>(); String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { queue.push(new EntryHolder( SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); queue.push(new EntryHolder( SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); Result result = invoker.invoke(invocation); result.whenCompleteWithContext((r, throwable) -> { Throwable error = throwable; if (error == null) { error = Optional.ofNullable(r).map(Result::getException).orElse(null); } while (!queue.isEmpty()) { EntryHolder holder = queue.pop(); Tracer.traceEntry(error, holder.entry); exitEntry(holder); } }); return result; } catch (BlockException e) { while (!queue.isEmpty()) { exitEntry(queue.pop()); } return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } } static class EntryHolder { final private Entry entry; final private Object[] params; public EntryHolder(Entry entry, Object[] params) { this.entry = entry; this.params = params; } } private void exitEntry(EntryHolder holder) { if (holder.params != null) { holder.entry.exit(1, holder.params); } else { holder.entry.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcException; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; /** *

Apache Dubbo service provider filter that enables integration with Sentinel. Auto activated by default.

*

Note: this only works for Apache Dubbo 2.7.x or above version.

*

* If you want to disable the provider filter, you can configure: *

 * <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
 * 
* * @author Carpenter Lee * @author Eric Zhao */ @Activate(group = PROVIDER) public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter { public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Apache Dubbo provider filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); if (null == origin) { origin = ""; } Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { // Only need to create entrance context at provider side, as context will take effect // at entrance of invocation chain only (for inbound traffic). ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboAdapterGlobalConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.config; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser; import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** *

* Responsible for dubbo service provider, consumer attribute configuration *

* * @author lianglin * @since 1.7.0 */ public final class DubboAdapterGlobalConfig { private static final String TRUE_STR = "true"; public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix"; private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled"; private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); public static boolean isUsePrefix() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); } public static String getDubboProviderResNamePrefixKey() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; } return null; } public static String getDubboConsumerResNamePrefixKey() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; } return null; } public static Boolean getDubboInterfaceGroupAndVersionEnabled() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED)); } public static DubboFallback getConsumerFallback() { return consumerFallback; } public static void setConsumerFallback(DubboFallback consumerFallback) { AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); DubboAdapterGlobalConfig.consumerFallback = consumerFallback; } public static DubboFallback getProviderFallback() { return providerFallback; } public static void setProviderFallback(DubboFallback providerFallback) { AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); DubboAdapterGlobalConfig.providerFallback = providerFallback; } /** * Get the origin parser of Dubbo adapter. * * @return the origin parser * @since 1.8.0 */ public static DubboOriginParser getOriginParser() { return originParser; } /** * Set the origin parser of Dubbo adapter. * * @param originParser the origin parser * @since 1.8.0 */ public static void setOriginParser(DubboOriginParser originParser) { AssertUtil.notNull(originParser, "originParser cannot be null"); DubboAdapterGlobalConfig.originParser = originParser; } private DubboAdapterGlobalConfig() {} } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; /** * @author Eric Zhao */ public class DefaultDubboFallback implements DubboFallback { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { // Just wrap the exception. return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; /** * Fallback handler for Dubbo services. * * @author Eric Zhao */ @FunctionalInterface public interface DubboFallback { /** * Handle the block exception and provide fallback result. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @param ex block exception * @return fallback result */ Result handle(Invoker invoker, Invocation invocation, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; /** *

Global fallback registry for Dubbo.

* * @author Eric Zhao * @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0. */ @Deprecated public final class DubboFallbackRegistry { public static DubboFallback getConsumerFallback() { return DubboAdapterGlobalConfig.getConsumerFallback(); } public static void setConsumerFallback(DubboFallback consumerFallback) { DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); } public static DubboFallback getProviderFallback() { return DubboAdapterGlobalConfig.getProviderFallback(); } public static void setProviderFallback(DubboFallback providerFallback) { DubboAdapterGlobalConfig.setProviderFallback(providerFallback); } private DubboFallbackRegistry() {} } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Default Dubbo origin parser. * * @author jingzian */ public class DefaultDubboOriginParser implements DubboOriginParser { @Override public String parse(Invoker invoker, Invocation invocation) { return DubboUtils.getApplication(invocation, ""); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.csp.sentinel.context.Context; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Customized origin parser for Dubbo provider filter.{@link Context#getOrigin()} * * @author jingzian */ public interface DubboOriginParser { /** * Parses the origin (caller) from Dubbo invocation. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @return the parsed origin */ String parse(Invoker invoker, Invocation invocation); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter ================================================ sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.adapter.dubbo.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.apache.dubbo.rpc.RpcContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; /** * Base test class, provide common methods for subClass * The package is same as CtSph, to call CtSph.resetChainMap() method for test *

* Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive * @author lianglin */ public class BaseTest extends AbstractTimeBasedTest { /** * Clean up resources for context, clusterNodeMap, processorSlotChainMap */ public void cleanUpAll() { try { clearDubboContext(); cleanUpCstContext(); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } private void cleanUpCstContext() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { ClusterBuilderSlot.getClusterNodeMap().clear(); CtSph.resetChainMap(); Method method = ContextUtil.class.getDeclaredMethod("resetContextMap"); method.setAccessible(true); method.invoke(null, null); ContextUtil.exit(); FlowRuleManager.loadRules(new ArrayList<>()); DegradeRuleManager.loadRules(new ArrayList<>()); } private void clearDubboContext() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); RpcContext.removeContext(); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import java.lang.reflect.Method; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author lianglin */ public class DubboTestUtil { public static Class DEFAULT_TEST_SERVICE = DemoService.class; public static Method DEFAULT_TEST_METHOD_ONE = DEFAULT_TEST_SERVICE.getMethods()[0]; public static Method DEFAULT_TEST_METHOD_TWO = DEFAULT_TEST_SERVICE.getMethods()[1]; public static Invoker getMockInvoker(URL url, Class cls) { Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(cls); return invoker; } public static Invoker getDefaultMockInvoker() { return getMockInvoker(getDefaultTestURL(), DEFAULT_TEST_SERVICE); } public static Invocation getMockInvocation(Method method) { Invocation invocation = mock(Invocation.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); return invocation; } public static Invocation getDefaultMockInvocationOne() { Invocation invocation = mock(Invocation.class); when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_ONE.getName()); when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_ONE.getParameterTypes()); return invocation; } public static Invocation getDefaultMockInvocationTwo() { Invocation invocation = mock(Invocation.class); when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_TWO.getName()); when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_TWO.getParameterTypes()); return invocation; } public static URL getDefaultTestURL() { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DEFAULT_TEST_SERVICE.getName()); return url; } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.util.TimeUtil; import org.mockito.MockedStatic; import org.mockito.Mockito; public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long timeInMs) { currentMillis += timeInMs; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import org.apache.dubbo.common.URL; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcContext; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboAppContextFilterTest extends BaseTest { private DubboAppContextFilter filter = new DubboAppContextFilter(); @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvokeApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application=serviceA"); when(invoker.getUrl()).thenReturn(url); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); String application = RpcContext.getContext().getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY); assertEquals("serviceA", application); } @Test public void testInvokeNullApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application="); when(invoker.getUrl()).thenReturn(url); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); String application = RpcContext.getContext().getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY); assertEquals(application, ""); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.config.SentinelConfig; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import java.util.HashMap; import static com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboUtilsTest { @Before public void setUp() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @After public void tearDown() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @Test public void testGetApplication() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(new HashMap<>()); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn("consumerA"); String application = DubboUtils.getApplication(invocation, ""); verify(invocation).getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""); assertEquals("consumerA", application); } @Test(expected = IllegalArgumentException.class) public void testGetApplicationNoAttachments() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(null); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn("consumerA"); DubboUtils.getApplication(invocation, ""); fail("No attachments in invocation, IllegalArgumentException should be thrown!"); } @Test public void testGetResourceName() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceName = DubboUtils.getMethodResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @Test public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceNameUseGroupAndVersion = DubboUtils.getMethodResourceName(invoker, invocation, true); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion); } @Test public void testGetResourceNameWithPrefix() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); //test with default prefix String resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); //test with custom prefix SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @Test public void testGetInterfaceName() { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", DubboUtils.getInterfaceName(invoker)); } @Test public void testGetInterfaceNameWithGroupAndVersion() throws NoSuchMethodException { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true"); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1", DubboUtils.getInterfaceName(invoker, true)); } @Test public void testGetInterfaceNameWithPrefix() throws NoSuchMethodException { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); //test with default prefix String resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); //test with custom prefix SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.util.TimeUtil; import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.support.RpcUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; import java.util.*; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; import static org.apache.dubbo.rpc.Constants.ASYNC_KEY; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive * @author lianglin */ @RunWith(MockitoJUnitRunner.class) public class SentinelDubboConsumerFilterTest extends BaseTest { private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); @Before public void setUp() { cleanUpAll(); initFallback(); } @After public void destroy() { cleanUpAll(); } @Test public void testInterfaceLevelFollowControlAsync() throws InterruptedException { Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initFlowRule(DubboUtils.getInterfaceName(invoker)); Result result1 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result1.getValue()); // should fallback because the qps > 1 Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset qps Thread.sleep(1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); verifyInvocationStructureForCallFinish(invoker, invocation); } @Test public void testDegradeAsync() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, 1740000000000L); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initDegradeRule(DubboUtils.getInterfaceName(invoker)); Result result = invokeDubboRpc(false, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { invokeDubboRpc(true, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); } Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset exception sleep(mocked, 1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); assertNull(context); } } @Test public void testDegradeSync() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, 1740000000000L); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); initDegradeRule(DubboUtils.getInterfaceName(invoker)); Result result = invokeDubboRpc(false, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { invokeDubboRpc(true, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); } Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset exception sleep(mocked, 1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); assertNull(context); } } @Test public void testMethodFlowControlAsync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initFlowRule(consumerFilter.getMethodName(invoker, invocation, null)); invokeDubboRpc(false, invoker, invocation); invokeDubboRpc(false, invoker, invocation); Invocation invocation2 = DubboTestUtil.getDefaultMockInvocationTwo(); Result result2 = invokeDubboRpc(false, invoker, invocation2); verifyInvocationStructureForCallFinish(invoker, invocation2); assertEquals("normal", result2.getValue()); // the method of invocation should be blocked Result fallback = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", fallback.getValue()); verifyInvocationStructureForCallFinish(invoker, invocation); } @Test public void testInvokeAsync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); final Result result = mock(Result.class); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructureForAsyncCall(invoker, invocation); return result; }); consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNotNull(context); } @Test public void testInvokeSync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(invoker, invocation); return result; }); consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(defaultContextName) * --InterfaceNode(interfaceName) * ----MethodNode(resourceName) */ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = getNode(resourceName, entranceNode); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in curEntry // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(2, childList.size()); DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(0, childList.size()); DefaultNode methodNode = getNode(resourceName, entranceNode); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry // nothing will bind to local context when use the AsyncEntry Entry curEntry = context.getCurEntry(); assertNull(curEntry); // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } private void verifyInvocationStructureForCallFinish(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNull(context); String methodResourceName = consumerFilter.getMethodName(invoker, invocation, null); Entry[] entries = (Entry[]) RpcContext.getContext().get(methodResourceName); assertNull(entries); } private DefaultNode getNode(String resourceName, DefaultNode root) { Queue queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { DefaultNode temp = queue.poll(); if (temp.getId().getName().equals(resourceName)) { return temp; } for (Node node : temp.getChildList()) { queue.offer((DefaultNode) node); } } return null; } private void initFlowRule(String resource) { FlowRule flowRule = new FlowRule(resource); flowRule.setCount(1); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); List flowRules = new ArrayList<>(); flowRules.add(flowRule); FlowRuleManager.loadRules(flowRules); } private void initDegradeRule(String resource) { DegradeRule degradeRule = new DegradeRule(resource) .setCount(0.5) .setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); List degradeRules = new ArrayList<>(); degradeRules.add(degradeRule); degradeRule.setTimeWindow(1); DegradeRuleManager.loadRules(degradeRules); } private void initFallback() { DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> { // boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); }); } private Result invokeDubboRpc(boolean exception, Invoker invoker, Invocation invocation) { Result result = null; InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); if (InvokeMode.SYNC == invokeMode) { result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal"); } else { result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) : AsyncRpcResult.newDefaultAsyncResult("normal", invocation); } when(invoker.invoke(invocation)).thenReturn(result); return consumerFilter.invoke(invoker, invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive * @author lianglin */ public class SentinelDubboProviderFilterTest extends BaseTest { private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter(); @Before public void setUp() { cleanUpAll(); } @After public void destroy() { cleanUpAll(); } @Test public void testInvoke() { final String originApplication = "consumerA"; URL url = DubboTestUtil.getDefaultTestURL(); url = url.addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); Invoker invoker = DubboTestUtil.getMockInvoker(url, DemoService.class); Invocation invocation = DubboTestUtil.getMockInvocation(DemoService.class.getMethods()[0]); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn(originApplication); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(result.getException()).thenReturn(new Exception()); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(originApplication, invoker, invocation); return result; }); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(methodResourceName) * --InterfaceNode(interfaceName) * ----MethodNode(methodResourceName) */ private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter String methodResourceName = filter.getMethodName(invoker, invocation, null); assertEquals(methodResourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(methodResourceName, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.IN); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(filter.getInterfaceName(invoker, null), interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(methodResourceName, methodResource.getName()); assertSame(EntryType.IN, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(1, methodOriginCountMap.size()); assertTrue(methodOriginCountMap.containsKey(originApplication)); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(1, interfaceOriginCountMap.size()); assertTrue(interfaceOriginCountMap.containsKey(originApplication)); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Result; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * @author Eric Zhao */ public class DubboFallbackRegistryTest { @Before public void setUp() { DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @After public void tearDown() { DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @Test public void testDefaultFallback() { // Test for default fallback. BlockException ex = new FlowException("xxx"); Result result = new DefaultDubboFallback().handle(null, null, ex); Assert.assertTrue("The result should carry exception", result.hasException()); Assert.assertTrue(BlockException.isBlockException(result.getException())); Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName())); } @Test public void testCustomFallback() { BlockException ex = new FlowException("xxx"); DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, e) -> AsyncRpcResult .newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); Result result = DubboAdapterGlobalConfig.getConsumerFallback() .handle(null, null, ex); Assert.assertFalse("The invocation should not fail", result.hasException()); Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.junit.After; import org.junit.Assert; import org.junit.Test; /** * @author tiecheng */ public class DubboOriginRegistryTest { @After public void cleanUp() { DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); } @Test(expected = IllegalArgumentException.class) public void testDefaultOriginParserFail() { DubboAdapterGlobalConfig.getOriginParser().parse(null, null); } @Test public void testDefaultOriginParserSuccess() { RpcInvocation invocation = new RpcInvocation(); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName, origin); } @Test public void testCustomOriginParser() { DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { @Override public String parse(Invoker invoker, Invocation invocation) { return invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "default") + "_" + invocation .getMethodName(); } }); RpcInvocation invocation = new RpcInvocation(); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals("default_null", origin); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_null", origin); invocation.setMethodName("hello"); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_hello", origin); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.provider; /** * @author leyou */ public interface DemoService { String sayHello(String name, int n); String sayHi(String name,int n); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.provider.impl; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; /** * @author leyou */ public class DemoServiceImpl implements DemoService { public String sayHello(String name, int n) { return "Hello " + name + ", " + n; } @Override public String sayHi(String name, int n) { return "Hi " + name + ", " + n; } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/resources/spring-dubbo-consumer-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/resources/spring-dubbo-provider-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/README.md ================================================ # Sentinel Apache Dubbo Adapter (for 3.0.5+) > Note: 中文文档请见[此处](https://sentinelguard.io/zh-cn/docs/open-source-framework-integrations.html)。 Sentinel Dubbo Adapter provides service consumer filter and provider filter for [Apache Dubbo](https://dubbo.apache.org/en/) services. **Note: This adapter only supports Apache Dubbo 3.0.5 and above.** For `org.apache:dubbo` 2.7.x, please use `sentinel-apache-dubbo-adapter` module instead. For legacy `com.alibaba:dubbo` 2.6.x, please use `sentinel-dubbo-adapter` module instead. To use Sentinel Dubbo 3.x Adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-apache-dubbo3-adapter x.y.z ``` The Sentinel filters are **enabled by default**. Once you add the dependency, the Dubbo services and methods will become protected resources in Sentinel, which can leverage Sentinel's flow control and overload protection ability when rules are configured. Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo). If you don't want the filters enabled, you can manually disable them. For example: ```xml ``` For more details of Dubbo filter, see [Dubbo filter documentation](https://cn.dubbo.apache.org/en/overview/mannual/java-sdk/tasks/extensibility/filter/). ## Dubbo resources The resource for Dubbo services has two granularities: service interface and service method. - Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` - Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` > **Note**: Dubbo *group+version+interface* level is also supported in Sentinel Apache Dubbo adapter. > You may just add `-Dcsp.sentinel.dubbo.interface.group.version.enabled=true` JVM property, > then the resource name of the Dubbo interface and method will be prefixed with group and version info. ## Flow control based on caller In many circumstances, it's also significant to control traffic flow based on the **caller**. For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider. If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name). Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`), and will bring the caller's name when doing resource protection. If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers. If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. > Note: The adapter provides support for customizing origin parsing logic as well. > You may register your own `DubboOriginParser` implementation to `DubboAdapterGlobalConfig`. ## Global fallback Sentinel Dubbo Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `DubboFallback` interface and then register to `DubboAdapterGlobalConfig`. If no fallback is configured, Sentinel will wrap the `BlockException` with a `RuntimeException` as the fallback result. Besides, we can also leverage [Dubbo mock mechanism](https://dubbo.apache.org/zh/docs3-v2/java-sdk/advanced-features-and-usage/service/service-downgrade/) to provide fallback implementation of degraded Dubbo services. ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-apache-dubbo3-adapter jar 1.8 1.8 3.0.9 com.alibaba.csp sentinel-core org.apache.dubbo dubbo ${apache.dubbo.version} provided junit junit test org.mockito mockito-inline test com.alibaba fastjson test ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/BaseSentinelDubboFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. * * @author Zechao Zheng */ public abstract class BaseSentinelDubboFilter { /** * Get method name of dubbo rpc * * @param invoker * @param invocation * @return */ abstract String getMethodName(Invoker invoker, Invocation invocation, String prefix); /** * Get interface name of dubbo rpc * * @param invoker * @return */ abstract String getInterfaceName(Invoker invoker, String prefix); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; import org.apache.dubbo.rpc.model.ApplicationModel; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; /** * Puts current consumer's application name in the attachment of each invocation. * * @author Eric Zhao */ @Activate(group = CONSUMER) public class DubboAppContextFilter implements ClusterFilter { private final String applicationName; public DubboAppContextFilter(ApplicationModel applicationModel) { this.applicationName = applicationModel.tryGetApplicationName(); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { if (applicationName != null && !applicationName.isEmpty()) { invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, applicationName); } return invoker.invoke(invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcContext; import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY; /** * @author Eric Zhao */ public final class DubboUtils { public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication"; public static String getApplication(Invocation invocation, String defaultValue) { if (invocation == null || invocation.getAttachments() == null) { throw new IllegalArgumentException("Bad invocation instance"); } // 1. try to get application from dubbo context String remoteApplication = invocation.getAttachment(REMOTE_APPLICATION_KEY); if (StringUtils.isEmpty(remoteApplication)) { remoteApplication = RpcContext.getServerAttachment().getAttachment(REMOTE_APPLICATION_KEY); } if (StringUtils.isNotEmpty(remoteApplication)) { return remoteApplication; } // 2. fallback to sentinel application return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); } public static String getMethodResourceName(Invoker invoker, Invocation invocation){ return getMethodResourceName(invoker, invocation, false); } public static String getMethodResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); buf.append(interfaceResource) .append(":") .append(invocation.getMethodName()) .append("("); boolean isFirst = true; for (Class clazz : invocation.getParameterTypes()) { if (!isFirst) { buf.append(","); } buf.append(clazz.getName()); isFirst = false; } buf.append(")"); return buf.toString(); } public static String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { return getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); } } public static String getInterfaceName(Invoker invoker) { return getInterfaceName(invoker, false); } public static String getInterfaceName(Invoker invoker, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); return useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); } public static String getInterfaceName(Invoker invoker, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { return getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); } } private DubboUtils() { } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; import org.apache.dubbo.rpc.support.RpcUtils; import java.util.LinkedList; import java.util.Optional; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; /** *

Dubbo service consumer filter for Sentinel. Auto activated by default.

*

* If you want to disable the consumer filter, you can configure: *

 * <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
 * 
* * @author Carpenter Lee * @author Eric Zhao * @author Lin Liang */ @Activate(group = CONSUMER) public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter implements ClusterFilter { public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Apache Dubbo3 consumer filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); if (InvokeMode.SYNC == invokeMode) { return syncInvoke(invoker, invocation); } else { return asyncInvoke(invoker, invocation); } } private Result syncInvoke(Invoker invoker, Invocation invocation) { Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } } } private Result asyncInvoke(Invoker invoker, Invocation invocation) { LinkedList queue = new LinkedList<>(); String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { queue.push(new EntryHolder( SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); queue.push(new EntryHolder( SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); Result result = invoker.invoke(invocation); result.whenCompleteWithContext((r, throwable) -> { Throwable error = throwable; if (error == null) { error = Optional.ofNullable(r).map(Result::getException).orElse(null); } while (!queue.isEmpty()) { EntryHolder holder = queue.pop(); Tracer.traceEntry(error, holder.entry); exitEntry(holder); } }); return result; } catch (BlockException e) { while (!queue.isEmpty()) { exitEntry(queue.pop()); } return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } } static class EntryHolder { final private Entry entry; final private Object[] params; public EntryHolder(Entry entry, Object[] params) { this.entry = entry; this.params = params; } } private void exitEntry(EntryHolder holder) { if (holder.params != null) { holder.entry.exit(1, holder.params); } else { holder.entry.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcException; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; /** *

Apache Dubbo service provider filter that enables integration with Sentinel. Auto activated by default.

*

Note: this only works for Apache Dubbo 2.7.x or above version.

*

* If you want to disable the provider filter, you can configure: *

 * <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
 * 
* * @author Carpenter Lee * @author Eric Zhao */ @Activate(group = PROVIDER) public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter implements Filter { public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Apache Dubbo3 provider filter initialized"); } @Override String getMethodName(Invoker invoker, Invocation invocation, String prefix) { return DubboUtils.getMethodResourceName(invoker, invocation, prefix); } @Override String getInterfaceName(Invoker invoker, String prefix) { return DubboUtils.getInterfaceName(invoker, prefix); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); if (null == origin) { origin = ""; } Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { // Only need to create entrance context at provider side, as context will take effect // at entrance of invocation chain only (for inbound traffic). ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/config/DubboAdapterGlobalConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.config; import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo3.origin.DefaultDubboOriginParser; import com.alibaba.csp.sentinel.adapter.dubbo3.origin.DubboOriginParser; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** *

* Responsible for dubbo service provider, consumer attribute configuration *

* * @author lianglin * @since 1.7.0 */ public final class DubboAdapterGlobalConfig { private static final String TRUE_STR = "true"; public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix"; private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled"; private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); public static boolean isUsePrefix() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); } public static String getDubboProviderResNamePrefixKey() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; } return null; } public static String getDubboConsumerResNamePrefixKey() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; } return null; } public static Boolean getDubboInterfaceGroupAndVersionEnabled() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED)); } public static DubboFallback getConsumerFallback() { return consumerFallback; } public static void setConsumerFallback(DubboFallback consumerFallback) { AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); DubboAdapterGlobalConfig.consumerFallback = consumerFallback; } public static DubboFallback getProviderFallback() { return providerFallback; } public static void setProviderFallback(DubboFallback providerFallback) { AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); DubboAdapterGlobalConfig.providerFallback = providerFallback; } /** * Get the origin parser of Dubbo adapter. * * @return the origin parser * @since 1.8.0 */ public static DubboOriginParser getOriginParser() { return originParser; } /** * Set the origin parser of Dubbo adapter. * * @param originParser the origin parser * @since 1.8.0 */ public static void setOriginParser(DubboOriginParser originParser) { AssertUtil.notNull(originParser, "originParser cannot be null"); DubboAdapterGlobalConfig.originParser = originParser; } private DubboAdapterGlobalConfig() {} } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DefaultDubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; /** * @author Eric Zhao */ public class DefaultDubboFallback implements DubboFallback { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { // Just wrap the exception. return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; /** * Fallback handler for Dubbo services. * * @author Eric Zhao */ @FunctionalInterface public interface DubboFallback { /** * Handle the block exception and provide fallback result. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @param ex block exception * @return fallback result */ Result handle(Invoker invoker, Invocation invocation, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.fallback; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; /** *

Global fallback registry for Dubbo.

* * @author Eric Zhao * @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0. */ @Deprecated public final class DubboFallbackRegistry { public static DubboFallback getConsumerFallback() { return DubboAdapterGlobalConfig.getConsumerFallback(); } public static void setConsumerFallback(DubboFallback consumerFallback) { DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); } public static DubboFallback getProviderFallback() { return DubboAdapterGlobalConfig.getProviderFallback(); } public static void setProviderFallback(DubboFallback providerFallback) { DubboAdapterGlobalConfig.setProviderFallback(providerFallback); } private DubboFallbackRegistry() {} } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DefaultDubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.origin; import com.alibaba.csp.sentinel.adapter.dubbo3.DubboUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Default Dubbo origin parser. * * @author jingzian */ public class DefaultDubboOriginParser implements DubboOriginParser { @Override public String parse(Invoker invoker, Invocation invocation) { return DubboUtils.getApplication(invocation, ""); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.origin; import com.alibaba.csp.sentinel.context.Context; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; /** * Customized origin parser for Dubbo provider filter.{@link Context#getOrigin()} * * @author jingzian */ public interface DubboOriginParser { /** * Parses the origin (caller) from Dubbo invocation. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @return the parsed origin */ String parse(Invoker invoker, Invocation invocation); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter ================================================ sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo3.SentinelDubboProviderFilter ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.filter.ClusterFilter ================================================ dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo3.DubboAppContextFilter sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo3.SentinelDubboConsumerFilter ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.adapter.dubbo3.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.apache.dubbo.rpc.RpcContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; /** * Base test class, provide common methods for subClass * The package is same as CtSph, to call CtSph.resetChainMap() method for test *

* Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive * @author lianglin */ public class BaseTest extends AbstractTimeBasedTest { /** * Clean up resources for context, clusterNodeMap, processorSlotChainMap */ public void cleanUpAll() { try { clearDubboContext(); cleanUpCstContext(); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } private void cleanUpCstContext() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { ClusterBuilderSlot.getClusterNodeMap().clear(); CtSph.resetChainMap(); Method method = ContextUtil.class.getDeclaredMethod("resetContextMap"); method.setAccessible(true); method.invoke(null, null); ContextUtil.exit(); FlowRuleManager.loadRules(new ArrayList<>()); DegradeRuleManager.loadRules(new ArrayList<>()); } private void clearDubboContext() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); RpcContext.removeContext(); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcInvocation; import java.lang.reflect.Method; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author lianglin */ public class DubboTestUtil { public static Class DEFAULT_TEST_SERVICE = DemoService.class; public static Method DEFAULT_TEST_METHOD_ONE = DEFAULT_TEST_SERVICE.getMethods()[0]; public static Method DEFAULT_TEST_METHOD_TWO = DEFAULT_TEST_SERVICE.getMethods()[1]; public static Invoker getMockInvoker(URL url, Class cls) { Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(cls); return invoker; } public static Invoker getDefaultMockInvoker() { return getMockInvoker(getDefaultTestURL(), DEFAULT_TEST_SERVICE); } public static Invocation getMockInvocation(Method method) { Invocation invocation = mock(RpcInvocation.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); return invocation; } public static Invocation getDefaultMockInvocationOne() { Invocation invocation = mock(RpcInvocation.class); when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_ONE.getName()); when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_ONE.getParameterTypes()); return invocation; } public static Invocation getDefaultMockInvocationTwo() { Invocation invocation = mock(RpcInvocation.class); when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_TWO.getName()); when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_TWO.getParameterTypes()); return invocation; } public static URL getDefaultTestURL() { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DEFAULT_TEST_SERVICE.getName()); return url; } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.util.TimeUtil; import org.mockito.MockedStatic; import org.mockito.Mockito; public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long timeInMs) { currentMillis += timeInMs; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.BaseTest; import org.apache.dubbo.common.URL; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboAppContextFilterTest extends BaseTest { @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvokeApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application=serviceA"); when(invoker.getUrl()).thenReturn(url); ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication(); applicationModel.getApplicationConfigManager().setApplication(new ApplicationConfig("serviceA")); DubboAppContextFilter filter = new DubboAppContextFilter(applicationModel); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); verify(invocation).setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "serviceA"); applicationModel.destroy(); } @Test public void testInvokeNullApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application="); when(invoker.getUrl()).thenReturn(url); ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication(); DubboAppContextFilter filter = new DubboAppContextFilter(applicationModel); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); verify(invocation, times(0)).setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "serviceA"); applicationModel.destroy(); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtilsTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; import com.alibaba.csp.sentinel.config.SentinelConfig; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import java.util.HashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboUtilsTest { @Before public void setUp() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @After public void tearDown() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @Test public void testGetApplication() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(new HashMap<>()); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn("consumerA"); String application = DubboUtils.getApplication(invocation, ""); verify(invocation).getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""); assertEquals("consumerA", application); } @Test(expected = IllegalArgumentException.class) public void testGetApplicationNoAttachments() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(null); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn("consumerA"); DubboUtils.getApplication(invocation, ""); fail("No attachments in invocation, IllegalArgumentException should be thrown!"); } @Test public void testGetResourceName() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceName = DubboUtils.getMethodResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @Test public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceNameUseGroupAndVersion = DubboUtils.getMethodResourceName(invoker, invocation, true); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion); } @Test public void testGetResourceNameWithPrefix() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); //test with default prefix String resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); //test with custom prefix SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @Test public void testGetInterfaceName() { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", DubboUtils.getInterfaceName(invoker)); } @Test public void testGetInterfaceNameWithGroupAndVersion() throws NoSuchMethodException { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true"); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:1.0.0:grp1", DubboUtils.getInterfaceName(invoker, true)); } @Test public void testGetInterfaceNameWithPrefix() throws NoSuchMethodException { URL url = URL.valueOf("dubbo://127.0.0.1:2181") .addParameter(CommonConstants.VERSION_KEY, "1.0.0") .addParameter(CommonConstants.GROUP_KEY, "grp1") .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); Invoker invoker = mock(Invoker.class); when(invoker.getUrl()).thenReturn(url); when(invoker.getInterface()).thenReturn(DemoService.class); //test with default prefix String resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); //test with custom prefix SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilterTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.util.TimeUtil; import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.support.RpcUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; import java.util.*; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; import static org.apache.dubbo.rpc.Constants.ASYNC_KEY; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive * @author lianglin */ @RunWith(MockitoJUnitRunner.class) public class SentinelDubboConsumerFilterTest extends BaseTest { private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); @Before public void setUp() { cleanUpAll(); initFallback(); } @After public void destroy() { cleanUpAll(); } @Test public void testInterfaceLevelFollowControlAsync() throws InterruptedException { Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initFlowRule(DubboUtils.getInterfaceName(invoker)); Result result1 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result1.getValue()); // should fallback because the qps > 1 Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset qps Thread.sleep(1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); verifyInvocationStructureForCallFinish(invoker, invocation); } @Test public void testDegradeAsync() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, 1740000000000L); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initDegradeRule(DubboUtils.getInterfaceName(invoker)); Result result = invokeDubboRpc(false, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { invokeDubboRpc(true, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); } Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset exception sleep(mocked, 1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); assertNull(context); } } @Test public void testDegradeSync() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, 1750000000000L); Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); initDegradeRule(DubboUtils.getInterfaceName(invoker)); Result result = invokeDubboRpc(false, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { invokeDubboRpc(true, invoker, invocation); verifyInvocationStructureForCallFinish(invoker, invocation); } Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); // sleeping 1000 ms to reset exception sleep(mocked, 1000); Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); assertNull(context); } } @Test public void testMethodFlowControlAsync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); initFlowRule(consumerFilter.getMethodName(invoker, invocation, null)); invokeDubboRpc(false, invoker, invocation); invokeDubboRpc(false, invoker, invocation); Invocation invocation2 = DubboTestUtil.getDefaultMockInvocationTwo(); Result result2 = invokeDubboRpc(false, invoker, invocation2); verifyInvocationStructureForCallFinish(invoker, invocation2); assertEquals("normal", result2.getValue()); // the method of invocation should be blocked Result fallback = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", fallback.getValue()); verifyInvocationStructureForCallFinish(invoker, invocation); } @Test public void testInvokeAsync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); final Result result = mock(Result.class); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructureForAsyncCall(invoker, invocation); return result; }); consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNotNull(context); } @Test public void testInvokeSync() { Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(invoker, invocation); return result; }); consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(defaultContextName) * --InterfaceNode(interfaceName) * ----MethodNode(resourceName) */ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = getNode(resourceName, entranceNode); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in curEntry // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(2, childList.size()); DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(0, childList.size()); DefaultNode methodNode = getNode(resourceName, entranceNode); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry // nothing will bind to local context when use the AsyncEntry Entry curEntry = context.getCurEntry(); assertNull(curEntry); // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } private void verifyInvocationStructureForCallFinish(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNull(context); String methodResourceName = consumerFilter.getMethodName(invoker, invocation, null); Entry[] entries = (Entry[]) RpcContext.getContext().get(methodResourceName); assertNull(entries); } private DefaultNode getNode(String resourceName, DefaultNode root) { Queue queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { DefaultNode temp = queue.poll(); if (temp.getId().getName().equals(resourceName)) { return temp; } for (Node node : temp.getChildList()) { queue.offer((DefaultNode) node); } } return null; } private void initFlowRule(String resource) { FlowRule flowRule = new FlowRule(resource); flowRule.setCount(1); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); List flowRules = new ArrayList<>(); flowRules.add(flowRule); FlowRuleManager.loadRules(flowRules); } private void initDegradeRule(String resource) { DegradeRule degradeRule = new DegradeRule(resource) .setCount(0.5) .setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); List degradeRules = new ArrayList<>(); degradeRules.add(degradeRule); degradeRule.setTimeWindow(1); DegradeRuleManager.loadRules(degradeRules); } private void initFallback() { DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> { // boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); }); } private Result invokeDubboRpc(boolean exception, Invoker invoker, Invocation invocation) { Result result = null; InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); if (InvokeMode.SYNC == invokeMode) { result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal"); } else { result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) : AsyncRpcResult.newDefaultAsyncResult("normal", invocation); } when(invoker.invoke(invocation)).thenReturn(result); return consumerFilter.invoke(invoker, invocation); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive * @author lianglin */ public class SentinelDubboProviderFilterTest extends BaseTest { private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter(); @Before public void setUp() { cleanUpAll(); } @After public void destroy() { cleanUpAll(); } @Test public void testInvoke() { final String originApplication = "consumerA"; URL url = DubboTestUtil.getDefaultTestURL(); url = url.addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); Invoker invoker = DubboTestUtil.getMockInvoker(url, DemoService.class); Invocation invocation = DubboTestUtil.getMockInvocation(DemoService.class.getMethods()[0]); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn(originApplication); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(result.getException()).thenReturn(new Exception()); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(originApplication, invoker, invocation); return result; }); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(methodResourceName) * --InterfaceNode(interfaceName) * ----MethodNode(methodResourceName) */ private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter String methodResourceName = filter.getMethodName(invoker, invocation, null); assertEquals(methodResourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(methodResourceName, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.IN); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(filter.getInterfaceName(invoker, null), interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(methodResourceName, methodResource.getName()); assertSame(EntryType.IN, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(1, methodOriginCountMap.size()); assertTrue(methodOriginCountMap.containsKey(originApplication)); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(1, interfaceOriginCountMap.size()); assertTrue(interfaceOriginCountMap.containsKey(originApplication)); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3; import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.ModuleModel; import org.junit.Assert; import org.junit.Test; import java.util.Set; public class SentinelFilterTest { @Test public void test() { ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication(); ModuleModel moduleModel = applicationModel.newModule(); Set filters = moduleModel.getExtensionLoader(Filter.class).getSupportedExtensionInstances(); Assert.assertTrue(filters.stream().anyMatch(f -> f instanceof SentinelDubboProviderFilter)); Set clusterFilters = moduleModel.getExtensionLoader(ClusterFilter.class).getSupportedExtensionInstances(); Assert.assertTrue(clusterFilters.stream().anyMatch(f -> f instanceof DubboAppContextFilter)); Assert.assertTrue(clusterFilters.stream().anyMatch(f -> f instanceof SentinelDubboConsumerFilter)); applicationModel.destroy(); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistryTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.fallback; import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcInvocation; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.mock; /** * @author Eric Zhao */ public class DubboFallbackRegistryTest { @Before public void setUp() { DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @After public void tearDown() { DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @Test public void testDefaultFallback() { // Test for default fallback. BlockException ex = new FlowException("xxx"); Result result = new DefaultDubboFallback().handle(null, mock(RpcInvocation.class), ex); Assert.assertTrue("The result should carry exception", result.hasException()); Assert.assertTrue(BlockException.isBlockException(result.getException())); Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName())); } @Test public void testCustomFallback() { BlockException ex = new FlowException("xxx"); DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, e) -> AsyncRpcResult .newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); Result result = DubboAdapterGlobalConfig.getConsumerFallback() .handle(null, mock(RpcInvocation.class), ex); Assert.assertFalse("The invocation should not fail", result.hasException()); Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginRegistryTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.origin; import com.alibaba.csp.sentinel.adapter.dubbo3.DubboUtils; import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; import com.alibaba.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.junit.After; import org.junit.Assert; import org.junit.Test; /** * @author tiecheng */ public class DubboOriginRegistryTest { @After public void cleanUp() { DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); } @Test(expected = IllegalArgumentException.class) public void testDefaultOriginParserFail() { DubboAdapterGlobalConfig.getOriginParser().parse(null, null); } @Test public void testDefaultOriginParserSuccess() { RpcInvocation invocation = new RpcInvocation(); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName, origin); } @Test public void testCustomOriginParser() { DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { @Override public String parse(Invoker invoker, Invocation invocation) { return invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "default") + "_" + invocation .getMethodName(); } }); RpcInvocation invocation = new RpcInvocation(); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals("default_null", origin); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_null", origin); invocation.setMethodName("hello"); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_hello", origin); } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.provider; /** * @author leyou */ public interface DemoService { String sayHello(String name, int n); String sayHi(String name,int n); } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/impl/DemoServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo3.provider.impl; import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; /** * @author leyou */ public class DemoServiceImpl implements DemoService { public String sayHello(String name, int n) { return "Hello " + name + ", " + n; } @Override public String sayHi(String name, int n) { return "Hi " + name + ", " + n; } } ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-consumer-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-provider-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/README.md ================================================ # Sentinel Apache Httpclient Adapter ## Introduction Sentinel provides integration for OkHttp client to enable flow control for web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-apache-httpclient-adapter x.y.z ``` We can use the `SentinelApacheHttpClientBuilder` when `CloseableHttpClient` at initialization, for example: ```java CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build(); ``` If we want to add some additional configurations, we can refer to the following code ```java HttpClientBuilder builder = new SentinelApacheHttpClientBuilder(); //builder Other Definitions CloseableHttpClient httpclient = builder.build(); ``` ## Configuration - `SentinelApacheHttpClientConfig` configuration: |name|description|type|default value||------|------------|------|-------||prefix|customize resource prefix|`String`|`httpclient:`||extractor|customize resource extractor|`ApacheHttpClientResourceExtractor`|`DefaultApacheHttpClientResourceExtractor`||fallback|handle request when it is blocked|`ApacheHttpClientFallback`|`DefaultApacheHttpClientFallback`| ### extractor (resource extractor) We can define `ApacheHttpClientResourceExtractor` to customize resource extractor replace `DefaultApacheHttpClientResourceExtractor` at `SentinelApacheHttpClientBuilder` default config, for example: httpclient:GET:/httpclient/back/1 ==> httpclient:GET:/httpclient/back/{id} ```java SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setExtractor(new ApacheHttpClientResourceExtractor() { @Override public String extractor(HttpRequestWrapper request) { String contains = "/httpclient/back/"; String uri = request.getRequestLine().getUri(); if (uri.startsWith(contains)) { uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; } return request.getMethod() + ":" + uri; } }); CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); ``` ### fallback (Block handling) We can define `ApacheHttpClientFallback` at `SentinelApacheHttpClientBuilder` default config, to handle request is blocked according to the actual scenario, for example: ```java public class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback { @Override public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) { // Just wrap and throw the exception. throw new SentinelRpcException(e); } } ``` ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-apache-httpclient-adapter jar 4.5.6 2.1.3.RELEASE 5.1.5.RELEASE com.alibaba.csp sentinel-core org.apache.httpcomponents httpclient ${apache.httpclient.version} provided junit junit test org.mockito mockito-core test com.alibaba fastjson test org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework.boot spring-boot-test ${spring.boot.version} test org.springframework spring-test ${spring-test.version} test ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientBuilder.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.http.HttpException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpExecutionAware; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.execchain.ClientExecChain; import java.io.IOException; /** * @author zhaoyuguang */ public class SentinelApacheHttpClientBuilder extends HttpClientBuilder { private final SentinelApacheHttpClientConfig config; public SentinelApacheHttpClientBuilder(){ this.config = new SentinelApacheHttpClientConfig(); } public SentinelApacheHttpClientBuilder(SentinelApacheHttpClientConfig config){ this.config = config; } @Override protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { return new ClientExecChain() { @Override public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext, HttpExecutionAware execAware) throws IOException, HttpException { Entry entry = null; try { String name = config.getExtractor().extractor(request); if (!StringUtil.isEmpty(config.getPrefix())) { name = config.getPrefix() + name; } entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); return mainExec.execute(route, request, clientContext, execAware); } catch (BlockException e) { return config.getFallback().handle(request, e); } catch (Throwable t) { Tracer.traceEntry(t, entry); throw t; } finally { if (entry != null) { entry.exit(); } } } }; } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.config; import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.DefaultApacheHttpClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback.ApacheHttpClientFallback; import com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback.DefaultApacheHttpClientFallback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author zhaoyuguang */ public class SentinelApacheHttpClientConfig { private String prefix = "httpclient:"; private ApacheHttpClientResourceExtractor extractor = new DefaultApacheHttpClientResourceExtractor(); private ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback(); public String getPrefix() { return prefix; } public void setPrefix(String prefix) { AssertUtil.notNull(prefix, "prefix cannot be null"); this.prefix = prefix; } public ApacheHttpClientResourceExtractor getExtractor() { return extractor; } public void setExtractor(ApacheHttpClientResourceExtractor extractor) { AssertUtil.notNull(extractor, "extractor cannot be null"); this.extractor = extractor; } public ApacheHttpClientFallback getFallback() { return fallback; } public void setFallback(ApacheHttpClientFallback fallback) { AssertUtil.notNull(fallback, "fallback cannot be null"); this.fallback = fallback; } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/ApacheHttpClientResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor; import org.apache.http.client.methods.HttpRequestWrapper; /** * @author zhaoyuguang */ public interface ApacheHttpClientResourceExtractor { String extractor(HttpRequestWrapper request); } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/DefaultApacheHttpClientResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor; import org.apache.http.client.methods.HttpRequestWrapper; /** * @author zhaoyuguang */ public class DefaultApacheHttpClientResourceExtractor implements ApacheHttpClientResourceExtractor { @Override public String extractor(HttpRequestWrapper request) { return request.getRequestLine().getUri(); } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.protocol.HttpContext; import java.io.IOException; /** * @author zhaoyuguang */ public interface ApacheHttpClientFallback { CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e); } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/DefaultApacheHttpClientFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.protocol.HttpContext; import java.io.IOException; /** * @author zhaoyuguang */ public class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback { @Override public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) { // Just wrap and throw the exception. throw new SentinelRpcException(e); } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.apache.httpclient.app.TestApplication; import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig; import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.io.IOException; import static org.junit.Assert.assertNotNull; /** * @author zhaoyuguang */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = { "server.port=8184" }) public class SentinelApacheHttpClientTest { @Value("${server.port}") private Integer port; @Test public void testSentinelOkHttpInterceptor0() throws Exception { CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build(); HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back"); System.out.println(getRemoteString(httpclient, httpGet)); ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:/httpclient/back"); assertNotNull(cn); Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); } @Test public void testSentinelOkHttpInterceptor1() throws Exception { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setExtractor(new ApacheHttpClientResourceExtractor() { @Override public String extractor(HttpRequestWrapper request) { String contains = "/httpclient/back/"; String uri = request.getRequestLine().getUri(); if (uri.startsWith(contains)) { uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; } return request.getMethod() + ":" + uri; } }); CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back/1"); System.out.println(getRemoteString(httpclient, httpGet)); ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:GET:/httpclient/back/{id}"); assertNotNull(cn); Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); } private String getRemoteString(CloseableHttpClient httpclient, HttpGet httpGet) throws IOException { String result; HttpContext context = new BasicHttpContext(); CloseableHttpResponse response; response = httpclient.execute(httpGet, context); try { HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "utf-8"); EntityUtils.consume(entity); } finally { response.close(); } httpclient.close(); return result; } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/TestApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author zhaoyuguang */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class); } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/controller/TestController.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.app.controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhaoyuguang */ @RestController public class TestController { @RequestMapping("/httpclient/back") public String back() { return "Welcome Back!"; } @RequestMapping("/httpclient/back/{id}") public String back(@PathVariable String id) { return "Welcome Back! " + id; } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfigTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.config; import org.junit.Test; /** * @author zhaoyuguang */ public class SentinelApacheHttpClientConfigTest { @Test(expected = IllegalArgumentException.class) public void testConfigSetPrefix() { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setPrefix(null); } @Test(expected = IllegalArgumentException.class) public void testConfigSetCleaner() { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setExtractor(null); } @Test(expected = IllegalArgumentException.class) public void testConfigSetFallback() { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setFallback(null); } } ================================================ FILE: sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallbackTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Test; /** * @author zhaoyuguang */ public class ApacheHttpClientFallbackTest { @Test(expected = SentinelRpcException.class) public void testDefaultOkHttpFallback() { BlockException e = new FlowException("xxx"); ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback(); fallback.handle(null, e); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/README.md ================================================ # Sentinel API Gateway Adapter Common The `sentinel-api-gateway-adapter-common` module provides common abstraction for API gateway flow control: - `GatewayFlowRule`: flow control rule specific for route or API defined in API gateway. This can be automatically converted to `FlowRule` or `ParamFlowRule`. - `ApiDefinition`: gateway API definition with a group of predicates ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-api-gateway-adapter-common jar com.alibaba.csp sentinel-core com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-transport-common provided junit junit test org.assertj assertj-core test org.mockito mockito-core test ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common; /** * @author Eric Zhao * @since 1.6.0 */ public final class SentinelGatewayConstants { public static final int APP_TYPE_GATEWAY = 1; public static final int RESOURCE_MODE_ROUTE_ID = 0; public static final int RESOURCE_MODE_CUSTOM_API_NAME = 1; public static final int PARAM_PARSE_STRATEGY_CLIENT_IP = 0; public static final int PARAM_PARSE_STRATEGY_HOST = 1; public static final int PARAM_PARSE_STRATEGY_HEADER = 2; public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3; public static final int PARAM_PARSE_STRATEGY_COOKIE = 4; public static final int URL_MATCH_STRATEGY_EXACT = 0; public static final int URL_MATCH_STRATEGY_PREFIX = 1; public static final int URL_MATCH_STRATEGY_REGEX = 2; public static final int PARAM_MATCH_STRATEGY_EXACT = 0; public static final int PARAM_MATCH_STRATEGY_PREFIX = 1; public static final int PARAM_MATCH_STRATEGY_REGEX = 2; public static final int PARAM_MATCH_STRATEGY_CONTAINS = 3; public static final String GATEWAY_CONTEXT_DEFAULT = "sentinel_gateway_context_default"; public static final String GATEWAY_CONTEXT_PREFIX = "sentinel_gateway_context$$"; public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$"; public static final String GATEWAY_NOT_MATCH_PARAM = "$NM"; public static final String GATEWAY_DEFAULT_PARAM = "$D"; private SentinelGatewayConstants() {} } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiDefinition.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.Objects; import java.util.Set; /** * A group of HTTP API patterns. * * @author Eric Zhao * @since 1.6.0 */ public class ApiDefinition { private String apiName; private Set predicateItems; public ApiDefinition() {} public ApiDefinition(String apiName) { this.apiName = apiName; } public String getApiName() { return apiName; } public ApiDefinition setApiName(String apiName) { this.apiName = apiName; return this; } public Set getPredicateItems() { return predicateItems; } public ApiDefinition setPredicateItems(Set predicateItems) { this.predicateItems = predicateItems; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ApiDefinition that = (ApiDefinition)o; if (!Objects.equals(apiName, that.apiName)) { return false; } return Objects.equals(predicateItems, that.predicateItems); } @Override public int hashCode() { int result = apiName != null ? apiName.hashCode() : 0; result = 31 * result + (predicateItems != null ? predicateItems.hashCode() : 0); return result; } @Override public String toString() { return "ApiDefinition{" + "apiName='" + apiName + '\'' + ", predicateItems=" + predicateItems + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiDefinitionChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.Set; /** * @author Eric Zhao * @since 1.6.0 */ public interface ApiDefinitionChangeObserver { /** * Notify the observer about the new gateway API definitions. * * @param apiDefinitions new set of gateway API definition */ void onChange(Set apiDefinitions); } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPathPredicateItem.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.Objects; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; /** * @author Eric Zhao * @since 1.6.0 */ public class ApiPathPredicateItem implements ApiPredicateItem { private String pattern; private int matchStrategy = SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT; public ApiPathPredicateItem setPattern(String pattern) { this.pattern = pattern; return this; } public ApiPathPredicateItem setMatchStrategy(int matchStrategy) { this.matchStrategy = matchStrategy; return this; } public String getPattern() { return pattern; } public int getMatchStrategy() { return matchStrategy; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ApiPathPredicateItem that = (ApiPathPredicateItem)o; if (matchStrategy != that.matchStrategy) { return false; } return Objects.equals(pattern, that.pattern); } @Override public int hashCode() { int result = pattern != null ? pattern.hashCode() : 0; result = 31 * result + matchStrategy; return result; } @Override public String toString() { return "ApiPathPredicateItem{" + "pattern='" + pattern + '\'' + ", matchStrategy=" + matchStrategy + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPredicateGroupItem.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.6.0 */ public class ApiPredicateGroupItem implements ApiPredicateItem { private final Set items = new HashSet<>(); public ApiPredicateGroupItem addItem(ApiPredicateItem item) { AssertUtil.notNull(item, "item cannot be null"); items.add(item); return this; } public Set getItems() { return items; } /*@Override public ApiPredicateItem and(ApiPredicateItem item) { AssertUtil.notNull(item, "item cannot be null"); return this.addItem(item); }*/ } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPredicateItem.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.6.0 */ public interface ApiPredicateItem { /** * Combine two {@link ApiPredicateItem}. * * @param item another predicate item * @return combined predicate group item */ /*default ApiPredicateItem and(ApiPredicateItem item) { AssertUtil.notNull(item, "item cannot be null"); return new ApiPredicateGroupItem() .addItem(this).addItem(item); }*/ } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.spi.SpiLoader; import com.alibaba.csp.sentinel.util.StringUtil; /** * Manager for gateway API definitions. * * @author Eric Zhao * @since 1.6.0 */ public final class GatewayApiDefinitionManager { private static final Map API_MAP = new ConcurrentHashMap<>(); private static final ApiDefinitionPropertyListener LISTENER = new ApiDefinitionPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); /** * The map keeps all found ApiDefinitionChangeObserver (class name as key). */ private static final Map API_CHANGE_OBSERVERS = new ConcurrentHashMap<>(); static { try { currentProperty.addListener(LISTENER); initializeApiChangeObserverSpi(); } catch (Throwable ex) { RecordLog.warn("[GatewayApiDefinitionManager] Failed to initialize", ex); ex.printStackTrace(); } } private static void initializeApiChangeObserverSpi() { List listeners = SpiLoader.of(ApiDefinitionChangeObserver.class).loadInstanceList(); for (ApiDefinitionChangeObserver e : listeners) { API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e); RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}" , e.getClass().getCanonicalName()); } } public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { RecordLog.info("[GatewayApiDefinitionManager] Registering new property to gateway API definition manager"); currentProperty.removeListener(LISTENER); property.addListener(LISTENER); currentProperty = property; } } /** * Load given gateway API definitions and apply to downstream observers. * * @param apiDefinitions set of gateway API definitions * @return true if updated, or else false */ public static boolean loadApiDefinitions(Set apiDefinitions) { return currentProperty.updateValue(apiDefinitions); } public static ApiDefinition getApiDefinition(final String apiName) { if (apiName == null) { return null; } return API_MAP.get(apiName); } public static Set getApiDefinitions() { return new HashSet<>(API_MAP.values()); } private static final class ApiDefinitionPropertyListener implements PropertyListener> { @Override public void configUpdate(Set set) { applyApiUpdateInternal(set); RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: {}", API_MAP); } @Override public void configLoad(Set set) { applyApiUpdateInternal(set); RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: {}", API_MAP); } private static synchronized void applyApiUpdateInternal(Set set) { if (set == null || set.isEmpty()) { API_MAP.clear(); notifyDownstreamListeners(new HashSet()); return; } Map map = new HashMap<>(set.size()); Set validSet = new HashSet<>(); for (ApiDefinition definition : set) { if (isValidApi(definition)) { map.put(definition.getApiName(), definition); validSet.add(definition); } } API_MAP.clear(); API_MAP.putAll(map); // propagate to downstream. notifyDownstreamListeners(validSet); } } private static void notifyDownstreamListeners(/*@Valid*/ final Set definitions) { try { for (Map.Entry entry : API_CHANGE_OBSERVERS.entrySet()) { entry.getValue().onChange(definitions); } } catch (Exception ex) { RecordLog.warn("[GatewayApiDefinitionManager] WARN: failed to notify downstream api listeners", ex); } } public static boolean isValidApi(ApiDefinition apiDefinition) { return apiDefinition != null && StringUtil.isNotBlank(apiDefinition.getApiName()) && apiDefinition.getPredicateItems() != null; } static void addApiChangeListener(ApiDefinitionChangeObserver listener) { AssertUtil.notNull(listener, "listener cannot be null"); API_CHANGE_OBSERVERS.put(listener.getClass().getCanonicalName(), listener); } static void removeApiChangeListener(Class clazz) { AssertUtil.notNull(clazz, "class cannot be null"); API_CHANGE_OBSERVERS.remove(clazz.getCanonicalName()); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/matcher/AbstractApiMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; /** * @author Eric Zhao * @since 1.6.0 */ public abstract class AbstractApiMatcher implements Predicate { protected final String apiName; protected final ApiDefinition apiDefinition; /** * We use {@link com.alibaba.csp.sentinel.util.function.Predicate} here as the min JDK version is 1.7. */ protected final Set> matchers = new HashSet<>(); public AbstractApiMatcher(ApiDefinition apiDefinition) { AssertUtil.notNull(apiDefinition, "apiDefinition cannot be null"); AssertUtil.assertNotBlank(apiDefinition.getApiName(), "apiName cannot be empty"); this.apiName = apiDefinition.getApiName(); this.apiDefinition = apiDefinition; try { initializeMatchers(); } catch (Exception ex) { RecordLog.warn("[GatewayApiMatcher] Failed to initialize internal matchers", ex); } } /** * Initialize the matchers. */ protected abstract void initializeMatchers(); @Override public boolean test(T t) { for (Predicate matcher : matchers) { if (matcher.test(t)) { return true; } } return false; } public String getApiName() { return apiName; } public ApiDefinition getApiDefinition() { return apiDefinition; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/GetGatewayApiDefinitionGroupCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.command; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.6.0 */ @CommandMapping(name = "gateway/getApiDefinitions", desc = "Fetch all customized gateway API groups") public class GetGatewayApiDefinitionGroupCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess(JSON.toJSONString(GatewayApiDefinitionManager.getApiDefinitions())); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/GetGatewayRuleCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.command; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.6.0 */ @CommandMapping(name = "gateway/getRules", desc = "Fetch all gateway rules") public class GetGatewayRuleCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess(JSON.toJSONString(GatewayRuleManager.getRules())); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.command; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.net.URLDecoder; import java.util.HashSet; import java.util.Set; /** * @author Eric Zhao * @since 1.6.0 */ @CommandMapping(name = "gateway/updateApiDefinitions", desc = "") public class UpdateGatewayApiDefinitionGroupCommandHandler implements CommandHandler { private static WritableDataSource> apiDefinitionWds = null; @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("Bad data")); } try { data = URLDecoder.decode(data, "utf-8"); } catch (Exception e) { RecordLog.info("Decode gateway API definition data error", e); return CommandResponse.ofFailure(e, "decode gateway API definition data error"); } RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {}", data); String result = SUCCESS_MSG; Set apiDefinitions = parseJson(data); GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions); if (!writeToDataSource(apiDefinitionWds, apiDefinitions)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } private static final String SUCCESS_MSG = "success"; private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)"; /** * Parse json data to set of {@link ApiDefinition}. * * Since the predicateItems of {@link ApiDefinition} is set of interface, * here we parse predicateItems to {@link ApiPathPredicateItem} temporarily. */ private Set parseJson(String data) { Set apiDefinitions = new HashSet<>(); JSONArray array = JSON.parseArray(data); for (Object obj : array) { JSONObject o = (JSONObject)obj; ApiDefinition apiDefinition = new ApiDefinition((o.getString("apiName"))); Set predicateItems = new HashSet<>(); JSONArray itemArray = o.getJSONArray("predicateItems"); if (itemArray != null) { predicateItems.addAll(itemArray.toJavaList(ApiPathPredicateItem.class)); } apiDefinition.setPredicateItems(predicateItems); apiDefinitions.add(apiDefinition); } return apiDefinitions; } /** * Write target value to given data source. * * @param dataSource writable data source * @param value target value to save * @param value type * @return true if write successful or data source is empty; false if error occurs */ private boolean writeToDataSource(WritableDataSource dataSource, T value) { if (dataSource != null) { try { dataSource.write(value); } catch (Exception e) { RecordLog.warn("Write data source failed", e); return false; } } return true; } public synchronized static WritableDataSource> getWritableDataSource() { return apiDefinitionWds; } public synchronized static void setWritableDataSource(WritableDataSource> apiDefinitionWds) { UpdateGatewayApiDefinitionGroupCommandHandler.apiDefinitionWds = apiDefinitionWds; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.command; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import java.net.URLDecoder; import java.util.Set; /** * @author Eric Zhao * @since 1.6.0 */ @CommandMapping(name = "gateway/updateRules", desc = "Update gateway rules") public class UpdateGatewayRuleCommandHandler implements CommandHandler { private static WritableDataSource> gatewayFlowWds = null; @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("Bad data")); } try { data = URLDecoder.decode(data, "utf-8"); } catch (Exception e) { RecordLog.info("Decode gateway rule data error", e); return CommandResponse.ofFailure(e, "decode gateway rule data error"); } RecordLog.info("[API Server] Receiving rule change (type: gateway rule): {}", data); String result = SUCCESS_MSG; Set flowRules = JSON.parseObject(data, new TypeReference>() { }); GatewayRuleManager.loadRules(flowRules); if (!writeToDataSource(gatewayFlowWds, flowRules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } /** * Write target value to given data source. * * @param dataSource writable data source * @param value target value to save * @param value type * @return true if write successful or data source is empty; false if error occurs */ private boolean writeToDataSource(WritableDataSource dataSource, T value) { if (dataSource != null) { try { dataSource.write(value); } catch (Exception e) { RecordLog.warn("Write data source failed", e); return false; } } return true; } public synchronized static WritableDataSource> getWritableDataSource() { return gatewayFlowWds; } public synchronized static void setWritableDataSource(WritableDataSource> gatewayFlowWds) { UpdateGatewayRuleCommandHandler.gatewayFlowWds = gatewayFlowWds; } private static final String SUCCESS_MSG = "success"; private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)"; } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/ConfigurableRequestItemParser.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; /** * delegate RequestItemParser, support add extractors to customize request item parse. *

* example: * if you want to get client real ip in multi nginx proxy, you can register SentinelGatewayFilter bean as follows * * ConfigurableRequestItemParser parser = new ConfigurableRequestItemParser<>(new ServerWebExchangeItemParser()); * List headerNames = Arrays.asList("X-Real-IP", "Client-IP"); * parser.addRemoteAddressExtractor(serverWebExchange -> { * for (String headerKey : headerNames) { * String remoteAddress = serverWebExchange.getRequest().getHeaders().getFirst(headerKey); * if (StringUtils.hasLength(remoteAddress)) { * return remoteAddress; * } * } * return null; * }); * return new SentinelGatewayFilter(parser); * * @author icodening * @date 2022.01.14 */ public class ConfigurableRequestItemParser implements RequestItemParser { private final List> pathExtractors = new ArrayList<>(2); private final List> remoteAddressExtractors = new ArrayList<>(2); private final List> headerExtractors = new ArrayList<>(2); private final List> urlParamExtractors = new ArrayList<>(2); private final List> cookieValueExtractors = new ArrayList<>(2); private final RequestItemParser delegate; public ConfigurableRequestItemParser(RequestItemParser delegate) { AssertUtil.notNull(delegate, "delegate can not be null"); this.delegate = delegate; } @Override public String getPath(T request) { for (Function extractor : pathExtractors) { String pathValue = extractor.apply(request); if (StringUtil.isNotBlank(pathValue)) { return pathValue; } } return delegate.getPath(request); } @Override public String getRemoteAddress(T request) { for (Function extractor : remoteAddressExtractors) { String remoteAddress = extractor.apply(request); if (StringUtil.isNotBlank(remoteAddress)) { return remoteAddress; } } return delegate.getRemoteAddress(request); } @Override public String getHeader(T request, String key) { for (BiFunction extractor : headerExtractors) { String headerValue = extractor.apply(request, key); if (StringUtil.isNotBlank(headerValue)) { return headerValue; } } return delegate.getHeader(request, key); } @Override public String getUrlParam(T request, String paramName) { for (BiFunction extractor : urlParamExtractors) { String urlParam = extractor.apply(request, paramName); if (StringUtil.isNotBlank(urlParam)) { return urlParam; } } return delegate.getUrlParam(request, paramName); } @Override public String getCookieValue(T request, String cookieName) { for (BiFunction extractor : cookieValueExtractors) { String cookie = extractor.apply(request, cookieName); if (StringUtil.isNotBlank(cookie)) { return cookie; } } return delegate.getCookieValue(request, cookieName); } public ConfigurableRequestItemParser addPathExtractor(Function extractor) { if (extractor == null) { return this; } pathExtractors.add(extractor); return this; } public ConfigurableRequestItemParser addRemoteAddressExtractor(Function extractor) { if (extractor == null) { return this; } remoteAddressExtractors.add(extractor); return this; } public ConfigurableRequestItemParser addHeaderExtractor(BiFunction extractor) { if (extractor == null) { return this; } headerExtractors.add(extractor); return this; } public ConfigurableRequestItemParser addUrlParamExtractor(BiFunction extractor) { if (extractor == null) { return this; } urlParamExtractors.add(extractor); return this; } public ConfigurableRequestItemParser addCookieValueExtractor(BiFunction extractor) { if (extractor == null) { return this; } cookieValueExtractors.add(extractor); return this; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; /** * @author Eric Zhao * @since 1.6.0 */ public class GatewayParamParser { private final RequestItemParser requestItemParser; public GatewayParamParser(RequestItemParser requestItemParser) { AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); this.requestItemParser = requestItemParser; } /** * Parse parameters for given resource from the request entity on condition of the rule predicate. * * @param resource valid resource name * @param request valid request * @param rulePredicate rule predicate indicating the rules to refer * @return the parameter array */ public Object[] parseParameterFor(String resource, T request, Predicate rulePredicate) { if (StringUtil.isEmpty(resource) || request == null || rulePredicate == null) { return new Object[0]; } Set gatewayRules = new HashSet<>(); Set predSet = new HashSet<>(); boolean hasNonParamRule = false; for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) { if (rule.getParamItem() != null) { gatewayRules.add(rule); predSet.add(rulePredicate.test(rule)); } else { hasNonParamRule = true; } } if (!hasNonParamRule && gatewayRules.isEmpty()) { return new Object[0]; } if (predSet.size() > 1 || predSet.contains(false)) { return new Object[0]; } int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size(); Object[] arr = new Object[size]; for (GatewayFlowRule rule : gatewayRules) { GatewayParamFlowItem paramItem = rule.getParamItem(); int idx = paramItem.getIndex(); String param = parseInternal(paramItem, request); arr[idx] = param; } if (hasNonParamRule) { arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM; } return arr; } private String parseInternal(GatewayParamFlowItem item, T request) { switch (item.getParseStrategy()) { case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP: return parseClientIp(item, request); case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST: return parseHost(item, request); case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER: return parseHeader(item, request); case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM: return parseUrlParameter(item, request); case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE: return parseCookie(item, request); default: return null; } } private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) { String clientIp = requestItemParser.getRemoteAddress(request); String pattern = item.getPattern(); if (StringUtil.isEmpty(pattern)) { return clientIp; } return parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern); } private String parseHeader(/*@Valid*/ GatewayParamFlowItem item, T request) { String headerKey = item.getFieldName(); String pattern = item.getPattern(); // TODO: what if the header has multiple values? String headerValue = requestItemParser.getHeader(request, headerKey); if (StringUtil.isEmpty(pattern)) { return headerValue; } // Match value according to regex pattern or exact mode. return parseWithMatchStrategyInternal(item.getMatchStrategy(), headerValue, pattern); } private String parseHost(/*@Valid*/ GatewayParamFlowItem item, T request) { String pattern = item.getPattern(); String host = requestItemParser.getHeader(request, "Host"); if (StringUtil.isEmpty(pattern)) { return host; } // Match value according to regex pattern or exact mode. return parseWithMatchStrategyInternal(item.getMatchStrategy(), host, pattern); } private String parseUrlParameter(/*@Valid*/ GatewayParamFlowItem item, T request) { String paramName = item.getFieldName(); String pattern = item.getPattern(); String param = requestItemParser.getUrlParam(request, paramName); if (StringUtil.isEmpty(pattern)) { return param; } // Match value according to regex pattern or exact mode. return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); } private String parseCookie(/*@Valid*/ GatewayParamFlowItem item, T request) { String cookieName = item.getFieldName(); String pattern = item.getPattern(); String param = requestItemParser.getCookieValue(request, cookieName); if (StringUtil.isEmpty(pattern)) { return param; } // Match value according to regex pattern or exact mode. return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); } private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) { if (value == null) { return null; } switch (matchStrategy) { case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT: return value.equals(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM; case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS: return value.contains(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM; case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX: Pattern regex = GatewayRegexCache.getRegexPattern(pattern); if (regex == null) { return value; } return regex.matcher(value).matches() ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM; default: return value; } } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayRegexCache.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author Eric Zhao * @since 1.6.2 */ public final class GatewayRegexCache { private static final Map REGEX_CACHE = new ConcurrentHashMap<>(); public static Pattern getRegexPattern(String pattern) { if (pattern == null) { return null; } return REGEX_CACHE.get(pattern); } public static boolean addRegexPattern(String pattern) { if (pattern == null) { return false; } try { Pattern regex = Pattern.compile(pattern); REGEX_CACHE.put(pattern, regex); return true; } catch (Exception ex) { RecordLog.warn("[GatewayRegexCache] Failed to compile the regex: " + pattern, ex); return false; } } public static void clear() { REGEX_CACHE.clear(); } private GatewayRegexCache() {} } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/RequestItemParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; /** * @author Eric Zhao * @since 1.6.0 */ public interface RequestItemParser { /** * Get API path from the request. * * @param request valid request * @return API path */ String getPath(T request); /** * Get remote address from the request. * * @param request valid request * @return remote address */ String getRemoteAddress(T request); /** * Get the header associated with the header key. * * @param request valid request * @param key valid header key * @return the header */ String getHeader(T request, String key); /** * Get the parameter value associated with the parameter name. * * @param request valid request * @param paramName valid parameter name * @return the parameter value */ String getUrlParam(T request, String paramName); /** * Get the cookie value associated with the cookie name. * * @param request valid request * @param cookieName valid cookie name * @return the cookie value * @since 1.7.0 */ String getCookieValue(T request, String cookieName); } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayFlowRule.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import java.util.Objects; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** * @author Eric Zhao * @since 1.6.0 */ public class GatewayFlowRule { private String resource; private int resourceMode = SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; private int grade = RuleConstant.FLOW_GRADE_QPS; private double count; private long intervalSec = 1; private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT; private int burst; /** * For throttle (rate limiting with queueing). */ private int maxQueueingTimeoutMs = 500; /** * For parameter flow control. If not set, the gateway rule will be * converted to normal flow rule. */ private GatewayParamFlowItem paramItem; public GatewayFlowRule() {} public GatewayFlowRule(String resource) { this.resource = resource; } public String getResource() { return resource; } public GatewayFlowRule setResource(String resource) { this.resource = resource; return this; } public int getResourceMode() { return resourceMode; } public GatewayFlowRule setResourceMode(int resourceMode) { this.resourceMode = resourceMode; return this; } public int getGrade() { return grade; } public GatewayFlowRule setGrade(int grade) { this.grade = grade; return this; } public int getControlBehavior() { return controlBehavior; } public GatewayFlowRule setControlBehavior(int controlBehavior) { this.controlBehavior = controlBehavior; return this; } public double getCount() { return count; } public GatewayFlowRule setCount(double count) { this.count = count; return this; } public long getIntervalSec() { return intervalSec; } public GatewayFlowRule setIntervalSec(long intervalSec) { this.intervalSec = intervalSec; return this; } public int getBurst() { return burst; } public GatewayFlowRule setBurst(int burst) { this.burst = burst; return this; } public GatewayParamFlowItem getParamItem() { return paramItem; } public GatewayFlowRule setParamItem(GatewayParamFlowItem paramItem) { this.paramItem = paramItem; return this; } public int getMaxQueueingTimeoutMs() { return maxQueueingTimeoutMs; } public GatewayFlowRule setMaxQueueingTimeoutMs(int maxQueueingTimeoutMs) { this.maxQueueingTimeoutMs = maxQueueingTimeoutMs; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GatewayFlowRule rule = (GatewayFlowRule)o; if (resourceMode != rule.resourceMode) { return false; } if (grade != rule.grade) { return false; } if (Double.compare(rule.count, count) != 0) { return false; } if (intervalSec != rule.intervalSec) { return false; } if (controlBehavior != rule.controlBehavior) { return false; } if (burst != rule.burst) { return false; } if (maxQueueingTimeoutMs != rule.maxQueueingTimeoutMs) { return false; } if (!Objects.equals(resource, rule.resource)) { return false; } return Objects.equals(paramItem, rule.paramItem); } @Override public int hashCode() { int result; long temp; result = resource != null ? resource.hashCode() : 0; result = 31 * result + resourceMode; result = 31 * result + grade; temp = Double.doubleToLongBits(count); result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + (int)(intervalSec ^ (intervalSec >>> 32)); result = 31 * result + controlBehavior; result = 31 * result + burst; result = 31 * result + maxQueueingTimeoutMs; result = 31 * result + (paramItem != null ? paramItem.hashCode() : 0); return result; } @Override public String toString() { return "GatewayFlowRule{" + "resource='" + resource + '\'' + ", resourceMode=" + resourceMode + ", grade=" + grade + ", count=" + count + ", intervalSec=" + intervalSec + ", controlBehavior=" + controlBehavior + ", burst=" + burst + ", maxQueueingTimeoutMs=" + maxQueueingTimeoutMs + ", paramItem=" + paramItem + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayParamFlowItem.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; /** * @author Eric Zhao * @since 1.6.0 */ public class GatewayParamFlowItem { /** * Should be set when applying to parameter flow rules. */ private Integer index; /** * Strategy for parsing item (e.g. client IP, arbitrary headers and URL parameters). */ private int parseStrategy; /** * Field to get (only required for arbitrary headers or URL parameters mode). */ private String fieldName; /** * Matching pattern. If not set, all values will be kept in LRU map. */ private String pattern; /** * Matching strategy for item value. */ private int matchStrategy = SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT; public Integer getIndex() { return index; } GatewayParamFlowItem setIndex(Integer index) { this.index = index; return this; } public int getParseStrategy() { return parseStrategy; } public GatewayParamFlowItem setParseStrategy(int parseStrategy) { this.parseStrategy = parseStrategy; return this; } public String getFieldName() { return fieldName; } public GatewayParamFlowItem setFieldName(String fieldName) { this.fieldName = fieldName; return this; } public String getPattern() { return pattern; } public GatewayParamFlowItem setPattern(String pattern) { this.pattern = pattern; return this; } public int getMatchStrategy() { return matchStrategy; } public GatewayParamFlowItem setMatchStrategy(int matchStrategy) { this.matchStrategy = matchStrategy; return this; } @Override public String toString() { return "GatewayParamFlowItem{" + "index=" + index + ", parseStrategy=" + parseStrategy + ", fieldName='" + fieldName + '\'' + ", pattern='" + pattern + '\'' + ", matchStrategy=" + matchStrategy + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; /** * @author Eric Zhao * @since 1.6.0 */ final class GatewayRuleConverter { static FlowRule toFlowRule(/*@Valid*/ GatewayFlowRule rule) { return new FlowRule(rule.getResource()) .setControlBehavior(rule.getControlBehavior()) .setCount(rule.getCount()) .setGrade(rule.getGrade()) .setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs()); } static ParamFlowItem generateNonMatchPassParamItem() { return new ParamFlowItem().setClassType(String.class.getName()) .setCount(1000_0000) .setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); } static ParamFlowItem generateNonMatchBlockParamItem() { return new ParamFlowItem().setClassType(String.class.getName()) .setCount(0) .setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); } static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) { return new ParamFlowRule(gatewayRule.getResource()) .setCount(gatewayRule.getCount()) .setGrade(gatewayRule.getGrade()) .setDurationInSec(gatewayRule.getIntervalSec()) .setBurstCount(gatewayRule.getBurst()) .setControlBehavior(gatewayRule.getControlBehavior()) .setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs()) .setParamIdx(idx); } /** * Convert a gateway rule to parameter flow rule, then apply the generated * parameter index to {@link GatewayParamFlowItem} of the rule. * * @param gatewayRule a valid gateway rule that should contain valid parameter items * @param idx generated parameter index (callers should guarantee it's unique and incremental) * @return converted parameter flow rule */ static ParamFlowRule applyToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) { ParamFlowRule paramRule = new ParamFlowRule(gatewayRule.getResource()) .setCount(gatewayRule.getCount()) .setGrade(gatewayRule.getGrade()) .setDurationInSec(gatewayRule.getIntervalSec()) .setBurstCount(gatewayRule.getBurst()) .setControlBehavior(gatewayRule.getControlBehavior()) .setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs()) .setParamIdx(idx); GatewayParamFlowItem gatewayItem = gatewayRule.getParamItem(); // Apply the current idx to gateway rule item. gatewayItem.setIndex(idx); // Apply for pattern-based parameters. String valuePattern = gatewayItem.getPattern(); if (valuePattern != null) { paramRule.getParamFlowItemList().add(generateNonMatchPassParamItem()); } return paramRule; } private GatewayRuleConverter() {} } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayRegexCache; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author Eric Zhao * @since 1.6.0 */ public final class GatewayRuleManager { /** * Gateway flow rule map: (resource, [rules...]) */ private static final Map> GATEWAY_RULE_MAP = new ConcurrentHashMap<>(); private static final Map> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>(); private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); private static final Set FIELD_REQUIRED_SET = new HashSet<>( Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM, SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER, SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) ); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(LISTENER); } private GatewayRuleManager() { } public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { RecordLog.info("[GatewayRuleManager] Registering new property to gateway flow rule manager"); currentProperty.removeListener(LISTENER); property.addListener(LISTENER); currentProperty = property; } } /** * Load all provided gateway rules into memory, while * previous rules will be replaced. * * @param rules rule set * @return true if updated, otherwise false */ public static boolean loadRules(Set rules) { return currentProperty.updateValue(rules); } public static Set getRules() { Set rules = new HashSet<>(); for (Set ruleSet : GATEWAY_RULE_MAP.values()) { rules.addAll(ruleSet); } return rules; } public static Set getRulesForResource(String resourceName) { if (StringUtil.isBlank(resourceName)) { return new HashSet<>(); } Set set = GATEWAY_RULE_MAP.get(resourceName); if (set == null) { return new HashSet<>(); } return new HashSet<>(set); } /** *

Get all converted parameter rules.

*

Note: caller SHOULD NOT modify the list and rules.

* * @param resourceName valid resource name * @return converted parameter rules */ public static List getConvertedParamRules(String resourceName) { if (StringUtil.isBlank(resourceName)) { return new ArrayList<>(); } return CONVERTED_PARAM_RULE_MAP.get(resourceName); } public static boolean isValidRule(GatewayFlowRule rule) { if (rule == null || StringUtil.isBlank(rule.getResource()) || rule.getResourceMode() < 0 || rule.getGrade() < 0 || rule.getCount() < 0 || rule.getBurst() < 0 || rule.getControlBehavior() < 0) { return false; } if (rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER && rule.getMaxQueueingTimeoutMs() < 0) { return false; } if (rule.getIntervalSec() <= 0) { return false; } GatewayParamFlowItem item = rule.getParamItem(); if (item != null) { return isValidParamItem(item); } return true; } static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) { if (item.getParseStrategy() < 0) { return false; } // Check required field name for item types. if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) { return false; } return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; } private static final class GatewayRulePropertyListener implements PropertyListener> { @Override public void configUpdate(Set conf) { applyGatewayRuleInternal(conf); RecordLog.info("[GatewayRuleManager] Gateway flow rules received: {}", GATEWAY_RULE_MAP); } @Override public void configLoad(Set conf) { applyGatewayRuleInternal(conf); RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: {}", GATEWAY_RULE_MAP); } private int getIdxInternal(Map idxMap, String resourceName) { // Prepare index map. if (!idxMap.containsKey(resourceName)) { idxMap.put(resourceName, 0); } return idxMap.get(resourceName); } private void cacheRegexPattern(/*@NonNull*/ GatewayParamFlowItem item) { String pattern = item.getPattern(); if (StringUtil.isNotEmpty(pattern) && item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) { if (GatewayRegexCache.getRegexPattern(pattern) == null) { GatewayRegexCache.addRegexPattern(pattern); } } } private synchronized void applyGatewayRuleInternal(Set conf) { if (conf == null || conf.isEmpty()) { applyToConvertedParamMap(new HashSet()); GATEWAY_RULE_MAP.clear(); return; } Map> gatewayRuleMap = new ConcurrentHashMap<>(); Map idxMap = new HashMap<>(); Set paramFlowRules = new HashSet<>(); Map> noParamMap = new HashMap<>(); for (GatewayFlowRule rule : conf) { if (!isValidRule(rule)) { RecordLog.warn("[GatewayRuleManager] Ignoring invalid rule when loading new rules: " + rule); continue; } String resourceName = rule.getResource(); if (rule.getParamItem() == null) { // Cache the rules with no parameter config, then skip. List noParamList = noParamMap.get(resourceName); if (noParamList == null) { noParamList = new ArrayList<>(); noParamMap.put(resourceName, noParamList); } noParamList.add(rule); } else { int idx = getIdxInternal(idxMap, resourceName); // Convert to parameter flow rule. if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) { idxMap.put(rule.getResource(), idx + 1); } cacheRegexPattern(rule.getParamItem()); } // Apply to the gateway rule map. Set ruleSet = gatewayRuleMap.get(resourceName); if (ruleSet == null) { ruleSet = new HashSet<>(); gatewayRuleMap.put(resourceName, ruleSet); } ruleSet.add(rule); } // Handle non-param mode rules. for (Map.Entry> e : noParamMap.entrySet()) { List rules = e.getValue(); if (rules == null || rules.isEmpty()) { continue; } for (GatewayFlowRule rule : rules) { int idx = getIdxInternal(idxMap, e.getKey()); // Always use the same index (the last position). paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx)); } } applyToConvertedParamMap(paramFlowRules); GATEWAY_RULE_MAP.clear(); GATEWAY_RULE_MAP.putAll(gatewayRuleMap); } private void applyToConvertedParamMap(Set paramFlowRules) { Map> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap( new ArrayList<>(paramFlowRules)); if (newRuleMap == null || newRuleMap.isEmpty()) { // No parameter flow rules, so clear all the metrics. for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) { ParameterMetricStorage.clearParamMetricForResource(resource); } RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules"); CONVERTED_PARAM_RULE_MAP.clear(); return; } // Clear unused parameter metrics. for (Map.Entry> entry : CONVERTED_PARAM_RULE_MAP.entrySet()) { String resource = entry.getKey(); if (!newRuleMap.containsKey(resource)) { ParameterMetricStorage.clearParamMetricForResource(resource); continue; } List newRuleList = newRuleMap.get(resource); List oldRuleList = new ArrayList<>(entry.getValue()); oldRuleList.removeAll(newRuleList); for (ParamFlowRule rule : oldRuleList) { ParameterMetric metric = ParameterMetricStorage.getParamMetricForResource(resource); if (null != metric) { metric.clearForRule(rule); } } } // Apply to converted rule map. CONVERTED_PARAM_RULE_MAP.clear(); CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap); RecordLog.info("[GatewayRuleManager] Converted internal param rules: {}", CONVERTED_PARAM_RULE_MAP); } } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.slot; import java.util.List; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; import com.alibaba.csp.sentinel.spi.Spi; /** * @author Eric Zhao * @since 1.6.1 */ @Spi(order = -4000) public class GatewayFlowSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkGatewayParamFlow(resource, count, args); fireEntry(context, resource, node, count, prioritized, args); } private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { if (args == null) { return; } List rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName()); if (rules == null || rules.isEmpty()) { return; } for (ParamFlowRule rule : rules) { // Initialize the parameter metrics. ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule); if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) { String triggeredParam = ""; if (args.length > rule.getParamIdx()) { Object value = args[rule.getParamIdx()]; triggeredParam = String.valueOf(value); } throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule); } } } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.slot; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; /** * @author Eric Zhao * @since 1.6.1 * * @deprecated since 1.7.2, we can use @Spi(order = -4000) to adjust the order of {@link GatewayFlowSlot}, * this class is reserved for compatibility with older versions. * * @see GatewayFlowSlot * @see DefaultSlotChainBuilder */ @Deprecated public class GatewaySlotChainBuilder extends DefaultSlotChainBuilder { } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayApiDefinitionGroupCommandHandler com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayRuleCommandHandler com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayApiDefinitionGroupCommandHandler com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayRuleCommandHandler ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot ================================================ com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManagerTest.java ================================================ package com.alibaba.csp.sentinel.adapter.gateway.common.api; import java.util.Collections; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class GatewayApiDefinitionManagerTest { @Test public void testIsValidApi() { ApiDefinition bad1 = new ApiDefinition(); ApiDefinition bad2 = new ApiDefinition("foo"); ApiDefinition good1 = new ApiDefinition("foo") .setPredicateItems(Collections.singleton(new ApiPathPredicateItem() .setPattern("/abc") )); assertFalse(GatewayApiDefinitionManager.isValidApi(bad1)); assertFalse(GatewayApiDefinitionManager.isValidApi(bad2)); assertTrue(GatewayApiDefinitionManager.isValidApi(good1)); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.function.Predicate; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Eric Zhao */ @SuppressWarnings("unchecked") public class GatewayParamParserTest { private final Predicate routeIdPredicate = new Predicate() { @Override public boolean test(GatewayFlowRule e) { return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; } }; private final Predicate apiNamePredicate = new Predicate() { @Override public boolean test(GatewayFlowRule e) { return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME; } }; @Test public void testParseParametersNoParamItem() { RequestItemParser itemParser = mock(RequestItemParser.class); GatewayParamParser parser = new GatewayParamParser<>(itemParser); // Create a fake request. Object request = new Object(); // Prepare gateway rules. Set rules = new HashSet<>(); String routeId1 = "my_test_route_A"; rules.add(new GatewayFlowRule(routeId1) .setCount(5) .setIntervalSec(1) ); rules.add(new GatewayFlowRule(routeId1) .setCount(10) .setControlBehavior(2) .setMaxQueueingTimeoutMs(1000) ); GatewayRuleManager.loadRules(rules); Object[] params = parser.parseParameterFor(routeId1, request, routeIdPredicate); assertThat(params.length).isEqualTo(1); } @Test public void testParseParametersWithItems() { RequestItemParser itemParser = mock(RequestItemParser.class); GatewayParamParser paramParser = new GatewayParamParser<>(itemParser); // Create a fake request. Object request = new Object(); // Prepare gateway rules. Set rules = new HashSet<>(); final String routeId1 = "my_test_route_A"; final String api1 = "my_test_route_B"; final String headerName = "X-Sentinel-Flag"; final String paramName = "p"; final String cookieName = "myCookie"; GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1) .setCount(10) .setIntervalSec(10); GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1) .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ); GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1) .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName(headerName) ); GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1) .setCount(20) .setIntervalSec(1) .setBurst(5) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1) .setCount(120) .setIntervalSec(10) .setBurst(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) ); GatewayFlowRule routeRule5 = new GatewayFlowRule(routeId1) .setCount(50) .setIntervalSec(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) .setFieldName(cookieName) ); GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); rules.add(routeRule1); rules.add(routeRule2); rules.add(routeRule3); rules.add(routeRule4); rules.add(routeRule5); rules.add(routeRuleNoParam); rules.add(apiRule1); GatewayRuleManager.loadRules(rules); final String expectedHost = "hello.test.sentinel"; final String expectedAddress = "66.77.88.99"; final String expectedHeaderValue1 = "Sentinel"; final String expectedUrlParamValue1 = "17"; final String expectedCookieValue1 = "Sentinel-Foo"; mockClientHostAddress(itemParser, expectedAddress); Map expectedHeaders = new HashMap() {{ put(headerName, expectedHeaderValue1); put("Host", expectedHost); }}; mockHeaders(itemParser, expectedHeaders); mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1); mockSingleCookie(itemParser, cookieName, expectedCookieValue1); Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); // Param length should be 6 (5 with parameters, 1 normal flow with generated constant) assertThat(params.length).isEqualTo(6); assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress); assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1); assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1); assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost); assertThat(params[routeRule5.getParamItem().getIndex()]).isEqualTo(expectedCookieValue1); assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM); assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero(); String expectedUrlParamValue2 = "fs"; mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue2); params = paramParser.parseParameterFor(api1, request, apiNamePredicate); assertThat(params.length).isEqualTo(1); assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2); } @Test public void testParseParametersWithEmptyItemPattern() { RequestItemParser itemParser = mock(RequestItemParser.class); GatewayParamParser paramParser = new GatewayParamParser<>(itemParser); // Create a fake request. Object request = new Object(); // Prepare gateway rules. Set rules = new HashSet<>(); final String routeId = "my_test_route_DS(*H"; final String headerName = "X-Sentinel-Flag"; GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId) .setCount(10) .setIntervalSec(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName(headerName) .setPattern("") .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT) ); rules.add(routeRule1); GatewayRuleManager.loadRules(rules); mockSingleHeader(itemParser, headerName, "Sent1nel"); Object[] params = paramParser.parseParameterFor(routeId, request, routeIdPredicate); assertThat(params.length).isEqualTo(1); // Empty pattern should not take effect. assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo("Sent1nel"); } @Test public void testParseParametersWithItemPatternMatching() { RequestItemParser itemParser = mock(RequestItemParser.class); GatewayParamParser paramParser = new GatewayParamParser<>(itemParser); // Create a fake request. Object request = new Object(); // Prepare gateway rules. Set rules = new HashSet<>(); final String routeId1 = "my_test_route_F&@"; final String api1 = "my_test_route_E5K"; final String headerName = "X-Sentinel-Flag"; final String paramName = "p"; String nameEquals = "Wow"; String nameContains = "warn"; String valueRegex = "\\d+"; GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1) .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName(headerName) .setPattern(nameEquals) .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT) ); GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1) .setCount(20) .setIntervalSec(1) .setBurst(5) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) .setPattern(nameContains) .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS) ); GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) .setPattern(valueRegex) .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) ); rules.add(routeRule1); rules.add(routeRule2); rules.add(apiRule1); GatewayRuleManager.loadRules(rules); mockSingleHeader(itemParser, headerName, nameEquals); mockSingleUrlParam(itemParser, paramName, nameContains); Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); assertThat(params.length).isEqualTo(2); assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(nameEquals); assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(nameContains); mockSingleHeader(itemParser, headerName, nameEquals + "_foo"); mockSingleUrlParam(itemParser, paramName, nameContains + "_foo"); params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); assertThat(params.length).isEqualTo(2); assertThat(params[routeRule1.getParamItem().getIndex()]) .isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); assertThat(params[routeRule2.getParamItem().getIndex()]) .isEqualTo(nameContains + "_foo"); mockSingleHeader(itemParser, headerName, "foo"); mockSingleUrlParam(itemParser, paramName, "foo"); params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); assertThat(params.length).isEqualTo(2); assertThat(params[routeRule1.getParamItem().getIndex()]) .isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); assertThat(params[routeRule2.getParamItem().getIndex()]) .isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); mockSingleUrlParam(itemParser, paramName, "23"); params = paramParser.parseParameterFor(api1, request, apiNamePredicate); assertThat(params.length).isEqualTo(1); assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo("23"); mockSingleUrlParam(itemParser, paramName, "some233"); params = paramParser.parseParameterFor(api1, request, apiNamePredicate); assertThat(params.length).isEqualTo(1); assertThat(params[apiRule1.getParamItem().getIndex()]) .isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM); } private void mockClientHostAddress(/*@Mock*/ RequestItemParser parser, String address) { when(parser.getRemoteAddress(any())).thenReturn(address); } private void mockHeaders(/*@Mock*/ RequestItemParser parser, Map headerMap) { for (Map.Entry e : headerMap.entrySet()) { when(parser.getHeader(any(), eq(e.getKey()))).thenReturn(e.getValue()); } } private void mockUrlParams(/*@Mock*/ RequestItemParser parser, Map paramMap) { for (Map.Entry e : paramMap.entrySet()) { when(parser.getUrlParam(any(), eq(e.getKey()))).thenReturn(e.getValue()); } } private void mockSingleUrlParam(/*@Mock*/ RequestItemParser parser, String key, String value) { when(parser.getUrlParam(any(), eq(key))).thenReturn(value); } private void mockSingleHeader(/*@Mock*/ RequestItemParser parser, String key, String value) { when(parser.getHeader(any(), eq(key))).thenReturn(value); } private void mockSingleCookie(/*@Mock*/ RequestItemParser parser, String key, String value) { when(parser.getCookieValue(any(), eq(key))).thenReturn(value); } @Before public void setUp() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet()); GatewayRuleManager.loadRules(new HashSet()); GatewayRegexCache.clear(); } @After public void tearDown() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet()); GatewayRuleManager.loadRules(new HashSet()); GatewayRegexCache.clear(); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayRegexCacheTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.param; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class GatewayRegexCacheTest { @Before public void setUp() { GatewayRegexCache.clear(); } @After public void tearDown() { GatewayRegexCache.clear(); } @Test public void testAddAndGetRegexPattern() { // Test for invalid pattern. assertThat(GatewayRegexCache.addRegexPattern("\\")).isFalse(); assertThat(GatewayRegexCache.addRegexPattern(null)).isFalse(); // Test for good pattern. String goodPattern = "\\d+"; assertThat(GatewayRegexCache.addRegexPattern(goodPattern)).isTrue(); assertThat(GatewayRegexCache.getRegexPattern(goodPattern)).isNotNull(); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverterTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class GatewayRuleConverterTest { @Test public void testConvertToFlowRule() { GatewayFlowRule rule = new GatewayFlowRule("routeId1") .setCount(10) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(1000); FlowRule flowRule = GatewayRuleConverter.toFlowRule(rule); assertEquals(rule.getResource(), flowRule.getResource()); assertEquals(rule.getCount(), flowRule.getCount(), 0.01); assertEquals(rule.getControlBehavior(), flowRule.getControlBehavior()); assertEquals(rule.getMaxQueueingTimeoutMs(), flowRule.getMaxQueueingTimeMs()); } @Test public void testConvertAndApplyToParamRule() { GatewayFlowRule routeRule1 = new GatewayFlowRule("routeId1") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ); int idx = 1; ParamFlowRule paramRule = GatewayRuleConverter.applyToParamRule(routeRule1, idx); assertEquals(routeRule1.getResource(), paramRule.getResource()); assertEquals(routeRule1.getCount(), paramRule.getCount(), 0.01); assertEquals(routeRule1.getControlBehavior(), paramRule.getControlBehavior()); assertEquals(routeRule1.getIntervalSec(), paramRule.getDurationInSec()); assertEquals(routeRule1.getBurst(), paramRule.getBurstCount()); assertEquals(idx, (int)paramRule.getParamIdx()); assertEquals(idx, (int)routeRule1.getParamItem().getIndex()); } } ================================================ FILE: sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManagerTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import java.util.HashSet; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class GatewayRuleManagerTest { @Test public void testLoadAndGetGatewayRules() { Set rules = new HashSet<>(); String ahasRoute = "ahas_route"; GatewayFlowRule rule1 = new GatewayFlowRule(ahasRoute) .setCount(500) .setIntervalSec(1); GatewayFlowRule rule2 = new GatewayFlowRule(ahasRoute) .setCount(20) .setIntervalSec(2) .setBurst(5) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ); GatewayFlowRule rule3 = new GatewayFlowRule("complex_route_ZZZ") .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ); rules.add(rule1); rules.add(rule2); rules.add(rule3); GatewayRuleManager.loadRules(rules); List convertedRules = GatewayRuleManager.getConvertedParamRules(ahasRoute); assertNotNull(convertedRules); assertEquals(0, (int)rule2.getParamItem().getIndex()); assertEquals(0, (int)rule3.getParamItem().getIndex()); assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1)); assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule2)); } @Test public void testIsValidRule() { GatewayFlowRule bad1 = new GatewayFlowRule(); GatewayFlowRule bad2 = new GatewayFlowRule("abc") .setIntervalSec(0); GatewayFlowRule bad3 = new GatewayFlowRule("abc") .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)); GatewayFlowRule bad4 = new GatewayFlowRule("abc") .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("p") .setPattern("def") .setMatchStrategy(-1) ); GatewayFlowRule good1 = new GatewayFlowRule("abc"); GatewayFlowRule good2 = new GatewayFlowRule("abc") .setParamItem(new GatewayParamFlowItem().setParseStrategy(0)); GatewayFlowRule good3 = new GatewayFlowRule("abc") .setParamItem(new GatewayParamFlowItem() .setMatchStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("Origin") .setPattern("def")); assertFalse(GatewayRuleManager.isValidRule(bad1)); assertFalse(GatewayRuleManager.isValidRule(bad2)); assertFalse(GatewayRuleManager.isValidRule(bad3)); assertFalse(GatewayRuleManager.isValidRule(bad4)); assertTrue(GatewayRuleManager.isValidRule(good1)); assertTrue(GatewayRuleManager.isValidRule(good2)); assertTrue(GatewayRuleManager.isValidRule(good3)); } @Before public void setUp() { GatewayRuleManager.loadRules(new HashSet()); } @After public void tearDown() { GatewayRuleManager.loadRules(new HashSet()); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/README.md ================================================ # Sentinel Dubbo Adapter > Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。 Sentinel Dubbo Adapter provides service consumer filter and provider filter for [Dubbo](https://dubbo.apache.org/en-us/) services. **Note: This adapter only supports legacy Dubbo 2.6.x version and below.** For new Apache Dubbo 2.7.x or above version, please use `sentinel-apache-dubbo-adapter` module instead. To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-dubbo-adapter x.y.z ``` The Sentinel filters are **enabled by default**. Once you add the dependency, the Dubbo services and methods will become protected resources in Sentinel, which can leverage Sentinel's flow control and guard ability when rules are configured. Demos can be found in [sentinel-demo-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-dubbo). If you don't want the filters enabled, you can manually disable them. For example: ```xml ``` For more details of Dubbo filter, see [Dubbo filter documentation](https://cn.dubbo.apache.org/en/overview/mannual/java-sdk/tasks/extensibility/filter/). ## Dubbo resources The resource for Dubbo services has two granularities: service interface and service method. - Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` - Service method:resourceName format is `interfaceName:methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` ## Flow control based on caller In many circumstances, it's also significant to control traffic flow based on the **caller**. For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider. If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name). Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`), and will bring the caller's name when doing resource protection. If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers. If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. > Note: Dubbo consumer does not provide its Dubbo application name when doing RPC, > so developers should manually put the application name into *attachment* at consumer side, > then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) > where consumer can carry application name information to provider automatically. > If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, > developers can manually put the application name into attachment with the key `dubboApplication`. > > Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser` > implementation to `DubboAdapterGlobalConfig`. ## Global fallback Sentinel Dubbo Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `DubboFallback` interface and then register to `DubboAdapterGlobalConfig`. If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result. Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services. ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-dubbo-adapter jar 2.6.6 com.alibaba.csp sentinel-core com.alibaba dubbo ${dubbo.version} provided junit junit test org.mockito mockito-core test com.alibaba fastjson test ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; /** * @author leyou */ abstract class AbstractDubboFilter implements Filter { protected String getMethodResourceName(Invoker invoker, Invocation invocation) { StringBuilder buf = new StringBuilder(64); buf.append(invoker.getInterface().getName()) .append(":") .append(invocation.getMethodName()) .append("("); boolean isFirst = true; for (Class clazz : invocation.getParameterTypes()) { if (!isFirst) { buf.append(","); } buf.append(clazz.getName()); isFirst = false; } buf.append(")"); return buf.toString(); } protected String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { if (StringUtil.isBlank(prefix)) { return getMethodResourceName(invoker, invocation); } StringBuilder buf = new StringBuilder(64); return buf.append(prefix) .append(getMethodResourceName(invoker, invocation)) .toString(); } protected String getInterfaceName(Invoker invoker) { return invoker.getInterface().getName(); } protected String getInterfaceName(Invoker invoker, String prefix) { if (StringUtil.isBlank(prefix)) { return getInterfaceName(invoker); } return prefix + getInterfaceName(invoker); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAdapterGlobalConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser; import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** *

Global config and callback registry of Dubbo legacy adapter.

* * @author lianglin * @author Eric Zhao * @since 1.7.0 */ public final class DubboAdapterGlobalConfig { private static final String TRUE_STR = "true"; public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix"; private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); public static boolean isUsePrefix() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); } public static String getDubboProviderPrefix() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; } return null; } public static String getDubboConsumerPrefix() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; } return null; } public static DubboFallback getConsumerFallback() { return consumerFallback; } public static void setConsumerFallback(DubboFallback consumerFallback) { AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); DubboAdapterGlobalConfig.consumerFallback = consumerFallback; } public static DubboFallback getProviderFallback() { return providerFallback; } public static void setProviderFallback(DubboFallback providerFallback) { AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); DubboAdapterGlobalConfig.providerFallback = providerFallback; } /** * Get the origin parser of Dubbo adapter. * * @return the origin parser * @since 1.8.0 */ public static DubboOriginParser getOriginParser() { return originParser; } /** * Set the origin parser of Dubbo adapter. * * @param originParser the origin parser * @since 1.8.0 */ public static void setOriginParser(DubboOriginParser originParser) { AssertUtil.notNull(originParser, "originParser cannot be null"); DubboAdapterGlobalConfig.originParser = originParser; } private DubboAdapterGlobalConfig() {} } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.dubbo.common.Constants; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcContext; import com.alibaba.dubbo.rpc.RpcException; import static com.alibaba.dubbo.common.Constants.CONSUMER; /** * Puts current consumer's application name in the attachment of each invocation. * * @author Eric Zhao */ @Activate(group = CONSUMER) public class DubboAppContextFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY); if (application != null) { RpcContext.getContext().setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, application); } return invoker.invoke(invocation); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.dubbo.rpc.Invocation; /** * @author Eric Zhao */ public final class DubboUtils { public static final String DUBBO_APPLICATION_KEY = "dubboApplication"; public static String getApplication(Invocation invocation, String defaultValue) { if (invocation == null || invocation.getAttachments() == null) { throw new IllegalArgumentException("Bad invocation instance"); } return invocation.getAttachment(DUBBO_APPLICATION_KEY, defaultValue); } private DubboUtils() {} } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; import static com.alibaba.dubbo.common.Constants.CONSUMER; /** *

Dubbo service consumer filter for Sentinel. Auto activated by default.

* * If you want to disable the consumer filter, you can configure: *
 * <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
 * 
* * @author leyou * @author Eric Zhao */ @Activate(group = CONSUMER) public class SentinelDubboConsumerFilter extends AbstractDubboFilter implements Filter { public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Dubbo consumer filter initialized"); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { Entry interfaceEntry = null; Entry methodEntry = null; try { String prefix = DubboAdapterGlobalConfig.getDubboConsumerPrefix(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodResourceName(invoker, invocation, prefix); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Throwable e = result.getException(); // Record common exception. Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } } } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; import static com.alibaba.dubbo.common.Constants.PROVIDER; /** *

Dubbo service provider filter for Sentinel. Auto activated by default.

* * If you want to disable the provider filter, you can configure: *
 * <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
 * 
* * @author leyou * @author Eric Zhao */ @Activate(group = PROVIDER) public class SentinelDubboProviderFilter extends AbstractDubboFilter implements Filter { public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Dubbo provider filter initialized"); } @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); if (null == origin) { origin = ""; } Entry interfaceEntry = null; Entry methodEntry = null; try { String prefix = DubboAdapterGlobalConfig.getDubboProviderPrefix(); String methodResourceName = getMethodResourceName(invoker, invocation, prefix); String interfaceName = getInterfaceName(invoker, prefix); ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Throwable e = result.getException(); // Record common exception. Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcResult; /** * @author Eric Zhao */ public class DefaultDubboFallback implements DubboFallback { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { // Just wrap the exception. edit by wzg923 2020/9/23 RpcResult result = new RpcResult(); result.setException(new SentinelRpcException(ex.toRuntimeException())); return result; } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; /** * Fallback handler for Dubbo services. * * @author Eric Zhao */ public interface DubboFallback { /** * Handle the block exception and provide fallback result. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @param ex block exception * @return fallback result */ Result handle(Invoker invoker, Invocation invocation, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; /** *

Global fallback registry for Dubbo.

* * @author Eric Zhao * @deprecated use {@link DubboAdapterGlobalConfig} instead. */ @Deprecated public final class DubboFallbackRegistry { public static DubboFallback getConsumerFallback() { return DubboAdapterGlobalConfig.getConsumerFallback(); } public static void setConsumerFallback(DubboFallback consumerFallback) { DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); } public static DubboFallback getProviderFallback() { return DubboAdapterGlobalConfig.getProviderFallback(); } public static void setProviderFallback(DubboFallback providerFallback) { DubboAdapterGlobalConfig.setProviderFallback(providerFallback); } private DubboFallbackRegistry() {} } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; /** * Default Dubbo origin parser. * * @author tiecheng * @since 1.8.0 */ public class DefaultDubboOriginParser implements DubboOriginParser { @Override public String parse(Invoker invoker, Invocation invocation) { return DubboUtils.getApplication(invocation, ""); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; /** * Customized origin parser for Dubbo provider filter. * * @author tiecheng * @since 1.8.0 */ public interface DubboOriginParser { /** * Parses the origin (caller) from Dubbo invocation. * * @param invoker Dubbo invoker * @param invocation Dubbo invocation * @return the parsed origin */ String parse(Invoker invoker, Invocation invocation); } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter ================================================ sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.dubbo.rpc.RpcContext; /** * Base test class, provide common methods for subClass * The package is same as CtSph, to call CtSph.resetChainMap() method for test * * Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive */ public class BaseTest { /** * Clean up resources for context, clusterNodeMap, processorSlotChainMap */ protected static void cleanUpAll() { RpcContext.removeContext(); ClusterBuilderSlot.getClusterNodeMap().clear(); CtSph.resetChainMap(); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author cdfive */ public class AbstractDubboFilterTest { @Before public void setUp() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); } @After public void tearDown() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); } private AbstractDubboFilter filter = new AbstractDubboFilter() { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { return null; } }; @Test public void testGetResourceName() { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getMethods()[0]; when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @Test public void testGetResourceNameWithPrefix() { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getMethods()[0]; when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); //test with default prefix String resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix()); System.out.println("resourceName = " + resourceName); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); //test with custom prefix SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.RpcContext; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboAppContextFilterTest extends BaseTest { private DubboAppContextFilter filter = new DubboAppContextFilter(); @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvokeApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application=serviceA"); when(invoker.getUrl()).thenReturn(url); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); String application = RpcContext.getContext().getAttachment(DubboUtils.DUBBO_APPLICATION_KEY); assertEquals("serviceA", application); } @Test public void testInvokeNullApplicationKey() { Invoker invoker = mock(Invoker.class); Invocation invocation = mock(Invocation.class); URL url = URL.valueOf("test://test:111/test?application="); when(invoker.getUrl()).thenReturn(url); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); String application = RpcContext.getContext().getAttachment(DubboUtils.DUBBO_APPLICATION_KEY); assertNull(application); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java ================================================ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.dubbo.rpc.Invocation; import org.junit.Test; import java.util.HashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.*; /** * @author cdfive */ public class DubboUtilsTest { @Test public void testGetApplication() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(new HashMap()); when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn("consumerA"); String application = DubboUtils.getApplication(invocation, ""); verify(invocation).getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, ""); assertEquals("consumerA", application); } @Test(expected = IllegalArgumentException.class) public void testGetApplicationNoAttachments() { Invocation invocation = mock(Invocation.class); when(invocation.getAttachments()).thenReturn(null); when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn("consumerA"); DubboUtils.getApplication(invocation, ""); fail("No attachments in invocation, IllegalArgumentException should be thrown!"); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive */ public class SentinelDubboConsumerFilterTest extends BaseTest { private SentinelDubboConsumerFilter filter = new SentinelDubboConsumerFilter(); @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvoke() { final Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); final Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getMethods()[0]; when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(invoker.invoke(invocation)).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { verifyInvocationStructure(invoker, invocation); return result; } }); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(defaultContextName) * --InterfaceNode(interfaceName) * ----MethodNode(resourceName) */ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DemoService.class.getName(), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in curEntry // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author cdfive */ public class SentinelDubboProviderFilterTest extends BaseTest { private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter(); @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvoke() { final String originApplication = "consumerA"; final Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); final Invocation invocation = mock(Invocation.class); Method method = DemoService.class.getMethods()[0]; when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn(originApplication); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(invoker.invoke(invocation)).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { verifyInvocationStructure(originApplication, invoker, invocation); return result; } }); filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: * EntranceNode(resourceName) * --InterfaceNode(interfaceName) * ----MethodNode(resourceName) */ private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals(resourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(resourceName, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.IN); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(DemoService.class.getName(), interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.IN, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(1, methodOriginCountMap.size()); assertTrue(methodOriginCountMap.containsKey(originApplication)); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(1, interfaceOriginCountMap.size()); assertTrue(interfaceOriginCountMap.containsKey(originApplication)); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcResult; import org.junit.Assert; import org.junit.Test; /** * @author Eric Zhao */ public class DubboFallbackRegistryTest { @Test public void testDefaultFallback() { // Test for default fallback. BlockException ex = new FlowException("xxx"); Result result = new DefaultDubboFallback().handle(null, null, ex); Assert.assertTrue(result.hasException()); Assert.assertEquals(SentinelRpcException.class, result.getException().getClass()); } @Test public void testCustomFallback() { BlockException ex = new FlowException("xxx"); DubboAdapterGlobalConfig.setConsumerFallback(new DubboFallback() { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException e) { return new RpcResult("Error: " + e.getClass().getName()); } }); Result result = DubboAdapterGlobalConfig.getConsumerFallback() .handle(null, null, ex); Assert.assertFalse("The invocation should not fail", result.hasException()); Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.origin; import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.RpcInvocation; import org.junit.After; import org.junit.Assert; import org.junit.Test; /** * @author tiecheng */ public class DubboOriginRegistryTest { @After public void cleanUp() { DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); } @Test(expected = IllegalArgumentException.class) public void testDefaultOriginParserFail() { DubboAdapterGlobalConfig.getOriginParser().parse(null, null); } @Test public void testDefaultOriginParserSuccess() { RpcInvocation invocation = new RpcInvocation(); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName, origin); } @Test public void testCustomOriginParser() { DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { @Override public String parse(Invoker invoker, Invocation invocation) { return invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "default") + "_" + invocation .getMethodName(); } }); RpcInvocation invocation = new RpcInvocation(); String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals("default_null", origin); String dubboName = "sentinel"; invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_null", origin); invocation.setMethodName("hello"); origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); Assert.assertEquals(dubboName + "_hello", origin); } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.provider; /** * @author leyou */ public interface DemoService { String sayHello(String name, int n); } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.dubbo.provider.impl; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; /** * @author leyou */ public class DemoServiceImpl implements DemoService { public String sayHello(String name, int n) { return "Hello " + name + ", " + n; } } ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-consumer-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-provider-filter.xml ================================================ ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/README.md ================================================ # Sentinel gRPC Adapter Sentinel gRPC Adapter provides client and server interceptor for gRPC services. > Note that currently the interceptor only supports unary methods in gRPC. ## Client Interceptor Example: ```java public class ServiceClient { private final ManagedChannel channel; ServiceClient(String host, int port) { this.channel = ManagedChannelBuilder.forAddress(host, port) .intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor. .build(); // Init your stub here. } } ``` ## Server Interceptor Example: ```java import io.grpc.Server; Server server = ServerBuilder.forPort(port) .addService(new MyServiceImpl()) // Add your service. .intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor. .build(); ``` ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-grpc-adapter jar 3.21.9 1.51.0 com.alibaba.csp sentinel-core io.grpc grpc-netty ${grpc.version} provided io.grpc grpc-protobuf ${grpc.version} provided io.grpc grpc-stub ${grpc.version} provided javax.annotation javax.annotation-api ${javax.annotation-api.version} junit junit test org.mockito mockito-core test com.alibaba fastjson ${fastjson.version} test kr.motd.maven os-maven-plugin 1.5.0.Final org.xolstice.maven.plugins protobuf-maven-plugin 0.5.1 com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} test-compile test-compile-custom ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; import io.grpc.ForwardingClientCall; import io.grpc.ForwardingClientCallListener; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; import javax.annotation.Nullable; import java.util.concurrent.atomic.AtomicReference; /** *

gRPC client interceptor for Sentinel. Currently it only works with unary methods.

*

* Example code: *

 * public class ServiceClient {
 *
 *     private final ManagedChannel channel;
 *
 *     ServiceClient(String host, int port) {
 *         this.channel = ManagedChannelBuilder.forAddress(host, port)
 *             .intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor.
 *             .build();
 *         // Init your stub here.
 *     }
 *
 * }
 * 
*

* For server interceptor, see {@link SentinelGrpcServerInterceptor}. * * @author Eric Zhao */ public class SentinelGrpcClientInterceptor implements ClientInterceptor { private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription( "Flow control limit exceeded (client side)"); @Override public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { String fullMethodName = methodDescriptor.getFullMethodName(); Entry entry = null; try { entry = SphU.asyncEntry(fullMethodName, EntryType.OUT); final AtomicReference atomicReferenceEntry = new AtomicReference<>(entry); // Allow access, forward the call. return new ForwardingClientCall.SimpleForwardingClientCall( channel.newCall(methodDescriptor, callOptions)) { @Override public void start(Listener responseListener, Metadata headers) { super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) { @Override public void onClose(Status status, Metadata trailers) { Entry entry = atomicReferenceEntry.get(); if (entry != null) { // Record the exception metrics. if (!status.isOk()) { Tracer.traceEntry(status.asRuntimeException(), entry); } entry.exit(); atomicReferenceEntry.set(null); } super.onClose(status, trailers); } }, headers); } /** * Some Exceptions will only call cancel. */ @Override public void cancel(@Nullable String message, @Nullable Throwable cause) { Entry entry = atomicReferenceEntry.get(); // Some Exceptions will call onClose and cancel. if (entry != null) { // Record the exception metrics. Tracer.traceEntry(cause, entry); entry.exit(); atomicReferenceEntry.set(null); } super.cancel(message, cause); } }; } catch (BlockException e) { // Flow control threshold exceeded, block the call. return new ClientCall() { @Override public void start(Listener responseListener, Metadata headers) { responseListener.onClose(FLOW_CONTROL_BLOCK, new Metadata()); } @Override public void request(int numMessages) { } @Override public void cancel(@Nullable String message, @Nullable Throwable cause) { } @Override public void halfClose() { } @Override public void sendMessage(ReqT message) { } }; } catch (RuntimeException e) { // Catch the RuntimeException newCall throws, entry is guaranteed to exit. if (entry != null) { Tracer.traceEntry(e, entry); entry.exit(); } throw e; } } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import io.grpc.ForwardingServerCall; import io.grpc.ForwardingServerCallListener; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.Status; import io.grpc.StatusRuntimeException; import java.util.concurrent.atomic.AtomicReference; /** *

gRPC server interceptor for Sentinel. Currently it only works with unary methods.

*

* Example code: *

 * Server server = ServerBuilder.forPort(port)
 *      .addService(new MyServiceImpl()) // Add your service.
 *      .intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor.
 *      .build();
 * 
*

* For client interceptor, see {@link SentinelGrpcClientInterceptor}. * * @author Eric Zhao */ public class SentinelGrpcServerInterceptor implements ServerInterceptor { private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription( "Flow control limit exceeded (server side)"); private static final StatusRuntimeException STATUS_RUNTIME_EXCEPTION = new StatusRuntimeException(Status.CANCELLED); @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { String fullMethodName = call.getMethodDescriptor().getFullMethodName(); // Remote address: serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); Entry entry = null; try { entry = SphU.asyncEntry(fullMethodName, EntryType.IN); final AtomicReference atomicReferenceEntry = new AtomicReference<>(entry); // Allow access, forward the call. return new ForwardingServerCallListener.SimpleForwardingServerCallListener( next.startCall( new ForwardingServerCall.SimpleForwardingServerCall(call) { @Override public void close(Status status, Metadata trailers) { Entry entry = atomicReferenceEntry.get(); if (entry != null) { // Record the exception metrics. if (!status.isOk()) { Tracer.traceEntry(status.asRuntimeException(), entry); } //entry exit when the call be closed entry.exit(); } super.close(status, trailers); } }, headers)) { /** * If call was canceled, onCancel will be called. and the close will not be called * so the server is encouraged to abort processing to save resources by onCancel * @see ServerCall.Listener#onCancel() */ @Override public void onCancel() { Entry entry = atomicReferenceEntry.get(); if (entry != null) { Tracer.traceEntry(STATUS_RUNTIME_EXCEPTION, entry); entry.exit(); atomicReferenceEntry.set(null); } super.onCancel(); } }; } catch (BlockException e) { call.close(FLOW_CONTROL_BLOCK, new Metadata()); return new ServerCall.Listener() { }; } catch (RuntimeException e) { // Catch the RuntimeException startCall throws, entry is guaranteed to exit. if (entry != null) { Tracer.traceEntry(e, entry); entry.exit(); } throw e; } } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooServiceGrpc; import io.grpc.ClientInterceptor; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.concurrent.TimeUnit; /** * A simple wrapped gRPC client for FooService. * * @author Eric Zhao */ final class FooServiceClient { private final ManagedChannel channel; private final FooServiceGrpc.FooServiceBlockingStub blockingStub; FooServiceClient(String host, int port) { this.channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build(); this.blockingStub = FooServiceGrpc.newBlockingStub(this.channel); } FooServiceClient(String host, int port, ClientInterceptor interceptor) { this.channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .intercept(interceptor) .build(); this.blockingStub = FooServiceGrpc.newBlockingStub(this.channel); } FooResponse sayHello(FooRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } return blockingStub.sayHello(request); } FooResponse anotherHello(FooRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } return blockingStub.anotherHello(request); } void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooServiceGrpc; import io.grpc.stub.StreamObserver; /** * Implementation of FooService defined in proto. */ class FooServiceImpl extends FooServiceGrpc.FooServiceImplBase { @Override public void sayHello(FooRequest request, StreamObserver responseObserver) { int id = request.getId(); switch (id) { // Exception test case -1: responseObserver.onError(new IllegalAccessException("The id is error!")); break; // RT test case -2: try { Thread.sleep(1000); } catch (InterruptedException e) { responseObserver.onError(e); break; } default: String message = String.format("Hello %s! Your ID is %d.", request.getName(), id); FooResponse response = FooResponse.newBuilder().setMessage(message).build(); responseObserver.onNext(response); responseObserver.onCompleted(); break; } } @Override public void anotherHello(FooRequest request, StreamObserver responseObserver) { int id = request.getId(); switch (id) { // Exception test case -1: responseObserver.onError(new IllegalAccessException("The id is error!")); break; // RT test case -2: try { Thread.sleep(1000); } catch (InterruptedException e) { responseObserver.onError(e); break; } default: String message = String.format("Good day, %s (%d)", request.getName(), id); FooResponse response = FooResponse.newBuilder().setMessage(message).build(); responseObserver.onNext(response); responseObserver.onCompleted(); break; } } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/GrpcTestServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; import java.util.concurrent.Executors; class GrpcTestServer { private Server server; GrpcTestServer() {} void start(int port, boolean shouldIntercept) throws IOException { if (server != null) { throw new IllegalStateException("Server already running!"); } ServerBuilder serverBuild = ServerBuilder.forPort(port) .addService(new FooServiceImpl()); if (shouldIntercept) { serverBuild.intercept(new SentinelGrpcServerInterceptor()); } server = serverBuild.build(); server.start(); } void stop() { if (server != null) { server.shutdown(); server = null; } } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import io.grpc.StatusRuntimeException; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * Test cases for {@link SentinelGrpcClientInterceptor}. * * @author Eric Zhao */ public class SentinelGrpcClientInterceptorTest { private final String fullMethodName = "com.alibaba.sentinel.examples.FooService/sayHello"; private final GrpcTestServer server = new GrpcTestServer(); private FooServiceClient client; private void configureFlowRule(int count) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setResource(fullMethodName) .setLimitApp("default") .as(FlowRule.class); FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testGrpcClientInterceptor() throws Exception { final int port = 19328; server.start(port, false); client = new FooServiceClient("localhost", port, new SentinelGrpcClientInterceptor()); configureFlowRule(Integer.MAX_VALUE); assertTrue(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(666).build())); ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(fullMethodName, EntryType.OUT); assertNotNull(clusterNode); assertEquals(1, clusterNode.totalPass()); // Not allowed to pass. configureFlowRule(0); // The second request will be blocked. assertFalse(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(666).build())); assertEquals(1, clusterNode.blockRequest()); configureFlowRule(Integer.MAX_VALUE); assertFalse(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(-1).build())); assertEquals(1, clusterNode.totalException()); configureFlowRule(Integer.MAX_VALUE); assertTrue(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(-2).build())); assertTrue(clusterNode.avgRt() >= 1000); server.stop(); } private boolean sendRequest(FooRequest request) { try { FooResponse response = client.sayHello(request); System.out.println("Response: " + response); return true; } catch (StatusRuntimeException ex) { System.out.println("Blocked, cause: " + ex.getMessage()); return false; } } @Before public void cleanUpBefore() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } @After public void cleanUpAfter() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.grpc; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest; import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import io.grpc.StatusRuntimeException; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * Test cases for {@link SentinelGrpcServerInterceptor}. * * @author Eric Zhao */ public class SentinelGrpcServerInterceptorTest { private final String resourceName = "com.alibaba.sentinel.examples.FooService/anotherHello"; private final GrpcTestServer server = new GrpcTestServer(); private FooServiceClient client; private void configureFlowRule(int count) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setResource(resourceName) .setLimitApp("default") .as(FlowRule.class); FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testGrpcServerInterceptor() throws Exception { final int port = 19329; server.start(port, true); client = new FooServiceClient("localhost", port); configureFlowRule(Integer.MAX_VALUE); assertTrue(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(666).build())); ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.IN); assertNotNull(clusterNode); assertEquals(1, clusterNode.totalPass()); // Not allowed to pass. configureFlowRule(0); // The second request will be blocked. assertFalse(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(666).build())); assertEquals(1, clusterNode.blockRequest()); configureFlowRule(Integer.MAX_VALUE); assertFalse(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(-1).build())); assertEquals(1, clusterNode.totalException()); configureFlowRule(Integer.MAX_VALUE); assertTrue(sendRequest(FooRequest.newBuilder().setName("Sentinel").setId(-2).build())); assertTrue(clusterNode.avgRt() >= 1000); server.stop(); } private boolean sendRequest(FooRequest request) { try { FooResponse response = client.anotherHello(request); System.out.println("Response: " + response); return true; } catch (StatusRuntimeException ex) { System.out.println("Blocked, cause: " + ex.getMessage()); return false; } } @Before public void cleanUpBefore() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } @After public void cleanUpAfter() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } } ================================================ FILE: sentinel-adapter/sentinel-grpc-adapter/src/test/proto/example.proto ================================================ syntax = "proto3"; option java_multiple_files = true; option java_package = "com.alibaba.csp.sentinel.adapter.grpc.gen"; option java_outer_classname = "FooProto"; package com.alibaba.sentinel.examples; message FooRequest { string name = 1; int32 id = 2; } message FooResponse { string message = 1; } // Example service definition. service FooService { rpc sayHello(FooRequest) returns (FooResponse) {} rpc anotherHello(FooRequest) returns (FooResponse) {} } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/README.md ================================================ # Sentinel adapter for JAX-RS Sentinel provides integration to enable fault-tolerance and flow control for JAX-RS web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-jax-rs-adapter x.y.z ``` ## SentinelJaxRsProviderFilter The `SentinelJaxRsProviderFilter` is auto activated in pure JAX-RS application. For Spring web applications you can configure with Spring bean: ```java @Configuration public class FilterConfig { @Bean public SentinelJaxRsProviderFilter sentinelJaxRsProviderFilter() { return new SentinelJaxRsProviderFilter(); } } ``` ## DefaultExceptionMapper Sentinel provides DefaultExceptionMapper to map Throwable to Response (with Status.INTERNAL_SERVER_ERROR), in order to let SentinelJaxRsProviderFilter to be called and exit the Sentinel entry. According to `3.3.4 Exceptions` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): > Checked exceptions and throwables that have not been mapped and cannot be thrown directly MUST be wrapped in a container-specific exception that is then thrown and allowed to propagate to the underlying container. If WebApplicationException or its subclasses are thrown, they'll be automatically converted to `Response` and can enter response filter. If other kind of exceptions were thrown, and not matched by custom exception mapper, then the response filter cannot be called. For this case, a default exception mapper maybe introduced. According to `4.4 Exception Mapping Providers` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): > When choosing an exception mapping provider to map an exception, an implementation MUST use the provider whose generic type is the nearest superclass of the exception. If two or more exception providers are applicable, the one with the highest priority MUST be chosen as described in Section 4.1.3. If user also provides customized exception mapper of `Throwable`, then user has the responsibility to convert it to response and then the response filter can be called. As describe in `6.7.1 exceptions` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): > A response mapped from an exception MUST be processed using the ContainerResponse filter chain and the WriteTo interceptor chain (if an entity is present in the mapped response). ## SentinelJaxRsClientTemplate For jax-rs client, we provide `SentinelJaxRsClientTemplate` you can use it like this: ```java Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); ``` or executeAsync like this: ```java Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { @Override public Future get() { return client.target(host).path(url).request() .async() .get(); } }); ``` When a request is blocked, Sentinel JAX-RS filter will return Response with status of `TOO_MANY_REQUESTS` indicating the request is rejected. You can customize it by implement your own `SentinelJaxRsFallback` and register to `SentinelJaxRsConfig`. ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-adapter ${revision} ../pom.xml sentinel-jax-rs-adapter jar 2.1.1 com.alibaba.csp sentinel-core javax.ws.rs javax.ws.rs-api ${javax.ws.rs-api.version} provided junit junit test org.springframework.boot spring-boot-starter-web 2.2.6.RELEASE test org.springframework.boot spring-boot-starter-test 2.4.13 test io.rest-assured rest-assured 4.3.0 test org.jboss.resteasy resteasy-spring-boot-starter 4.5.1.Final test org.apache.maven.plugins maven-surefire-plugin false ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; import com.alibaba.csp.sentinel.adapter.jaxrs.future.FutureWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.function.Supplier; import javax.ws.rs.core.Response; import java.util.concurrent.Future; /** * wrap jax-rs client execution with sentinel *

 *         Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() {
 *
 *             @Override
 *             public Response get() {
 *                 return client.target(host).path(url).request()
 *                         .get();
 *             }
 *         });
 * 
* @author sea */ public class SentinelJaxRsClientTemplate { /** * execute supplier with sentinel * @param resourceName * @param supplier * @return */ public static Response execute(String resourceName, Supplier supplier) { Entry entry = null; try { entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); return supplier.get(); } catch (BlockException ex) { return SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(resourceName, ex); } finally { System.out.println("entry exit"); if (entry != null) { entry.exit(); } } } /** * execute supplier with sentinel * @param resourceName * @param supplier * @return */ public static Future executeAsync(String resourceName, Supplier> supplier) { try { AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); return new FutureWrapper<>(entry, supplier.get()); } catch (BlockException ex) { return SentinelJaxRsConfig.getJaxRsFallback().fallbackFutureResponse(resourceName, ex); } } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.StringUtil; import javax.ws.rs.container.*; import javax.ws.rs.core.Context; import javax.ws.rs.ext.Provider; import java.io.IOException; /** * @author sea */ @Provider public class SentinelJaxRsProviderFilter implements ContainerRequestFilter, ContainerResponseFilter { private static final String SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME = "sentinel_jax_rs_provider_context"; private static final String SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY = "sentinel_jax_rs_provider_entry_property"; @Context private ResourceInfo resourceInfo; @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { try { String resourceName = getResourceName(containerRequestContext, resourceInfo); if (StringUtil.isNotEmpty(resourceName)) { // Parse the request origin using registered origin parser. String origin = parseOrigin(containerRequestContext); String contextName = getContextName(containerRequestContext); ContextUtil.enter(contextName, origin); Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); containerRequestContext.setProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY, entry); } } catch (BlockException e) { try { containerRequestContext.abortWith(SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(containerRequestContext.getUriInfo().getPath(), e)); } finally { ContextUtil.exit(); } } } @Override public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException { Entry entry = (Entry) containerRequestContext.getProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY); if (entry != null) { entry.exit(); } containerRequestContext.removeProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY); ContextUtil.exit(); } public String getResourceName(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) { return SentinelJaxRsConfig.getResourceNameParser().parse(containerRequestContext, resourceInfo); } protected String getContextName(ContainerRequestContext request) { return SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME; } protected String parseOrigin(ContainerRequestContext request) { return SentinelJaxRsConfig.getRequestOriginParser().parseOrigin(request); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.config; import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.DefaultSentinelJaxRsFallback; import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultRequestOriginParser; import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultResourceNameParser; import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; import com.alibaba.csp.sentinel.adapter.jaxrs.request.ResourceNameParser; /** * @author sea */ public class SentinelJaxRsConfig { private static volatile ResourceNameParser resourceNameParser = new DefaultResourceNameParser(); private static volatile RequestOriginParser requestOriginParser = new DefaultRequestOriginParser(); private static volatile SentinelJaxRsFallback jaxRsFallback = new DefaultSentinelJaxRsFallback(); public static ResourceNameParser getResourceNameParser() { return resourceNameParser; } public static void setResourceNameParser(ResourceNameParser resourceNameParser) { SentinelJaxRsConfig.resourceNameParser = resourceNameParser; } public static RequestOriginParser getRequestOriginParser() { return requestOriginParser; } public static void setRequestOriginParser(RequestOriginParser originParser) { SentinelJaxRsConfig.requestOriginParser = originParser; } public static SentinelJaxRsFallback getJaxRsFallback() { return jaxRsFallback; } public static void setJaxRsFallback(SentinelJaxRsFallback jaxRsFallback) { SentinelJaxRsConfig.jaxRsFallback = jaxRsFallback; } private SentinelJaxRsConfig() { } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.exception; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * sentinel jax-rs adapter provide this exception mapper * in case of user throw exception which is not {@link javax.ws.rs.WebApplicationException} and not matched by any ExceptionMapper * this exception mapper convert exception to Response let ContainerResponseFilter to be called to exit sentinel entry * user can add custom ExceptionMapper and config with {@link javax.annotation.Priority} with lower value * @author sea */ @Provider public class DefaultExceptionMapper implements ExceptionMapper { @Override public Response toResponse(Throwable exception) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(exception.getMessage()) .build(); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.fallback; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /** * @author sea */ public class DefaultSentinelJaxRsFallback implements SentinelJaxRsFallback { @Override public Response fallbackResponse(String route, Throwable cause) { return Response.status(Response.Status.TOO_MANY_REQUESTS) .entity("Blocked by Sentinel (flow limiting)") .type(MediaType.APPLICATION_JSON_TYPE) .build(); } @Override public Future fallbackFutureResponse(final String route, final Throwable cause) { return new FutureTask<>(new Callable() { @Override public Response call() throws Exception { return fallbackResponse(route, cause); } }); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.fallback; import javax.ws.rs.core.Response; import java.util.concurrent.Future; /** * @author sea */ public interface SentinelJaxRsFallback { /** * Provides a fallback response based on the cause of the failed execution. * * @param route The route the fallback is for * @param cause cause of the main method failure, may be null * @return the fallback response */ Response fallbackResponse(String route, Throwable cause); /** * Provides a fallback response future based on the cause of the failed execution. * * @param route The route the fallback is for * @param cause cause of the main method failure, may be null * @return the fallback response future */ Future fallbackFutureResponse(String route, Throwable cause); } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.future; import com.alibaba.csp.sentinel.AsyncEntry; import java.util.concurrent.*; /** * wrap Future to ensure entry exit * @author sea */ public class FutureWrapper implements Future { AsyncEntry entry; Future future; public FutureWrapper(AsyncEntry entry, Future future) { this.entry = entry; this.future = future; } @Override public boolean cancel(boolean mayInterruptIfRunning) { try { return future.cancel(mayInterruptIfRunning); } finally { exitEntry(); } } @Override public boolean isCancelled() { return future.isCancelled(); } @Override public boolean isDone() { return future.isDone(); } @Override public V get() throws InterruptedException, ExecutionException { try { return future.get(); } finally { exitEntry(); } } @Override public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return future.get(timeout, unit); } finally { exitEntry(); } } private void exitEntry() { if (entry != null) { entry.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.request; import javax.ws.rs.container.ContainerRequestContext; /** * @author sea */ public class DefaultRequestOriginParser implements RequestOriginParser { private static final String EMPTY_ORIGIN = ""; @Override public String parseOrigin(ContainerRequestContext request) { return EMPTY_ORIGIN; } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.request; import javax.ws.rs.Path; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; /** * @author sea */ public class DefaultResourceNameParser implements ResourceNameParser { @Override public String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) { Path classPath = resourceInfo.getResourceClass().getAnnotation(Path.class); Path methodPath = resourceInfo.getResourceMethod().getAnnotation(Path.class); return containerRequestContext.getRequest().getMethod() + ":" + (classPath != null ? classPath.value() : "") + (methodPath != null ? methodPath.value() : ""); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.request; import javax.ws.rs.container.ContainerRequestContext; /** * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. * * @author sea */ public interface RequestOriginParser { /** * Parse the origin from given HTTP request. * * @param request HTTP request * @return parsed origin */ String parseOrigin(ContainerRequestContext request); } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs.request; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; /** * @author sea */ public interface ResourceNameParser { String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo); } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.CtSph; import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Supplier; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.SocketUtils; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.lang.reflect.Method; import java.util.Collections; import java.util.concurrent.*; import static org.junit.Assert.*; import static org.junit.Assert.assertNull; /** * @author sea */ public class ClientFilterTest { private static final String HELLO_STR = "Hello!"; static int port; static String host; static Client client; static ConfigurableApplicationContext ctx; @BeforeClass public static void startApplication() { client = ClientBuilder.newBuilder() .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(3, TimeUnit.SECONDS) .build(); port = SocketUtils.findAvailableTcpPort(); host = "http://127.0.0.1:" + port; SpringApplication springApplication = new SpringApplication(TestApplication.class); ctx = springApplication.run("--spring.profiles.active=client", "--server.port=" + port); } @AfterClass public static void shutdown() { ctx.close(); Context context = ContextUtil.getContext(); if (context != null) { context.setCurEntry(null); ContextUtil.exit(); } Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); // Clear chainMap in CtSph try { Method resetChainMapMethod = CtSph.class.getDeclaredMethod("resetChainMap"); resetChainMapMethod.setAccessible(true); resetChainMapMethod.invoke(null); } catch (Exception e) { // Empty } } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } @Test public void testClientGetHello() { final String url = "/test/hello"; String resourceName = "GET:" + url; Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); assertEquals(200, response.getStatus()); assertEquals(HELLO_STR, response.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); } @Test public void testClientAsyncGetHello() throws InterruptedException, ExecutionException { final String url = "/test/async-hello"; final String resourceName = "GET:" + url; Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { @Override public Future get() { return client.target(host).path(url).request() .async() .get(); } }); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(HELLO_STR, future.get().readEntity(String.class)); cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testCustomResourceName() { final String url = "/test/hello/{name}"; final String resourceName = "GET:" + url; Response response1 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host) .path(url) .resolveTemplate("name", "abc") .request() .get(); } }); assertEquals(200, response1.getStatus()); assertEquals("Hello abc !", response1.readEntity(String.class)); Response response2 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host) .path(url) .resolveTemplate("name", "def") .request() .get(); } }); assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response2.getStatus()); assertEquals("Hello def !", response2.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(2, cn.passQps(), 0.01); assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/abc")); assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/def")); } @Test public void testClientFallback() { final String url = "/test/hello"; final String resourceName = "GET:" + url; configureRulesFor(resourceName, 0); Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); assertEquals(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode(), response.getStatus()); assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testClientCustomFallback() { final String url = "/test/hello"; final String resourceName = "GET:" + url; configureRulesFor(resourceName, 0); SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { @Override public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) .entity("Blocked by Sentinel (flow limiting)") .type(MediaType.APPLICATION_JSON_TYPE) .build(); } @Override public Future fallbackFutureResponse(final String route, final Throwable cause) { return new FutureTask<>(new Callable() { @Override public Response call() throws Exception { return fallbackResponse(route, cause); } }); } }); Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response.getStatus()); assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testServerReturn400() { final String url = "/test/400"; final String resourceName = "GET:" + url; Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); assertEquals("test return 400", response.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testServerReturn500() { final String url = "/test/ex"; final String resourceName = "GET:" + url; Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); assertEquals("test exception mapper", response.readEntity(String.class)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testServerTimeout() { final String url = "/test/delay/10"; final String resourceName = "GET:/test/delay/{seconds}"; try { SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); } catch (ProcessingException e) { //ignore } ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testFutureGetServerTimeout() { final String url = "/test/delay/10"; final String resourceName = "GET:/test/delay/{seconds}"; try { Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { @Override public Future get() { return client.target(host).path(url).request() .async() .get(); } }); future.get(); } catch (Exception e) { //ignore } ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testFutureGetTimeout() { final String url = "/test/delay/10"; final String resourceName = "GET:/test/delay/{seconds}"; try { Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { @Override public Future get() { return client.target(host).path(url).request() .async() .get(); } }); future.get(1, TimeUnit.SECONDS); } catch (Exception e) { //ignore } ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testCancelFuture() { final String url = "/test/delay/10"; final String resourceName = "GET:/test/delay/{seconds}"; try { Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { @Override public Future get() { return client.target(host).path(url).request() .async() .get(); } }); future.cancel(false); } catch (Exception e) { //ignore } ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import java.util.Collections; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import io.restassured.RestAssured; import io.restassured.response.Response; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.SocketUtils; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.MediaType; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.*; /** * @author sea */ public class ProviderFilterTest { private static final String HELLO_STR = "Hello!"; static ConfigurableApplicationContext ctx; @BeforeClass public static void startApplication() { RestAssured.basePath = ""; int port = SocketUtils.findAvailableTcpPort(); RestAssured.port = port; SpringApplication springApplication = new SpringApplication(TestApplication.class); ctx = springApplication.run("--spring.profiles.active=provider", "--server.port=" + port); } @AfterClass public static void shutdown() { ctx.close(); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } @Test public void testGetHello() { String url = "/test/hello"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); } @Test public void testAsyncGetHello() { String url = "/test/async-hello"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); } @Test public void testUrlPathParam() { String url = "/test/hello/{name}"; String resourceName = "GET:" + url; String url1 = "/test/hello/abc"; Response response1 = given().get(url1); response1.then().statusCode(200).body(equalTo("Hello abc !")); String url2 = "/test/hello/def"; Response response2 = given().get(url2); response2.then().statusCode(200).body(equalTo("Hello def !")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(2, cn.passQps(), 0.01); assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url1)); assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url2)); } @Test public void testDefaultFallback() { String url = "/test/hello"; String resourceName = "GET:" + url; configureRulesFor(resourceName, 0); Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testCustomFallback() { String url = "/test/hello"; String resourceName = "GET:" + url; SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { @Override public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) .entity("Blocked by Sentinel (flow limiting)") .type(MediaType.APPLICATION_JSON_TYPE) .build(); } @Override public Future fallbackFutureResponse(final String route, final Throwable cause) { return new FutureTask<>(new Callable() { @Override public javax.ws.rs.core.Response call() throws Exception { return fallbackResponse(route, cause); } }); } }); configureRulesFor(resourceName, 0); Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.OK.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testCustomRequestOriginParser() { String url = "/test/hello"; String resourceName = "GET:" + url; String limitOrigin = "appB"; final String headerName = "X-APP"; configureRulesFor(resourceName, 0, limitOrigin); SentinelJaxRsConfig.setRequestOriginParser(new RequestOriginParser() { @Override public String parseOrigin(ContainerRequestContext request) { String origin = request.getHeaderString(headerName); return origin != null ? origin : ""; } }); Response response = given() .header(headerName, "appA").get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); Response blockedResp = given() .header(headerName, "appB") .get(url); blockedResp.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertEquals(1, cn.blockQps(), 0.01); } @Test public void testExceptionMapper() { String url = "/test/ex"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).body(equalTo("test exception mapper")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); } private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author sea */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.jaxrs; import org.springframework.stereotype.Component; import javax.ws.rs.*; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @author sea */ @Path("/test") @Component public class TestResource { ExecutorService executor = Executors.newFixedThreadPool(5); @Path("/hello") @GET @Produces({ MediaType.APPLICATION_JSON }) public String sayHello() { return "Hello!"; } @Path("/async-hello") @GET @Produces({ MediaType.APPLICATION_JSON }) public void asyncSayHello(@Suspended final AsyncResponse asyncResponse) { executor.submit(new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } asyncResponse.resume("Hello!"); } }); } @Path("/hello/{name}") @GET @Produces({ MediaType.APPLICATION_JSON }) public String sayHelloWithName(@PathParam(value = "name") String name) { return "Hello " + name + " !"; } @Path("/ex") @GET @Produces({ MediaType.APPLICATION_JSON }) public String exception() { throw new RuntimeException("test exception mapper"); } @Path("/400") @GET @Produces({ MediaType.APPLICATION_JSON }) public String badRequest() { throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity("test return 400") .build()); } @Path("/delay/{seconds}") @GET @Produces({ MediaType.APPLICATION_JSON }) public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException { TimeUnit.SECONDS.sleep(seconds); return "finish"; } } ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml ================================================ resteasy: jaxrs: scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs.exception ================================================ FILE: sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml ================================================ resteasy: jaxrs: scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-motan-adapter jar 1.1.8 com.alibaba.csp sentinel-core com.weibo motan-core ${motan.version} provided com.weibo motan-transport-netty4 ${motan.version} provided ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/MotanUtils.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan; import com.alibaba.csp.sentinel.adapter.motan.config.MotanAdapterGlobalConfig; import com.alibaba.csp.sentinel.util.StringUtil; import com.weibo.api.motan.rpc.Caller; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.util.ReflectUtil; /** * @author zhangxn8 */ public class MotanUtils { private MotanUtils() {} public static String getMethodResourceName(Caller caller, Request request){ return getMethodResourceName(caller, request, false); } public static String getMethodResourceName(Caller caller, Request request, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); String interfaceResource = useGroupAndVersion ? caller.getUrl().getPath(): caller.getInterface().getName(); buf.append(interfaceResource) .append(":") .append(request.getMethodName()) .append("("); boolean isFirst = true; try { Class[] classTypes = ReflectUtil.forNames(request.getParamtersDesc()); for (Class clazz : classTypes) { if (!isFirst) { buf.append(","); } buf.append(clazz.getName()); isFirst = false; } } catch (ClassNotFoundException e) { e.printStackTrace(); } buf.append(")"); return buf.toString(); } public static String getMethodResourceName(Caller caller, Request request, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getMethodResourceName(caller, request,MotanAdapterGlobalConfig.getMotanInterfaceGroupAndVersionEnabled())) .toString(); } else { return getMethodResourceName(caller, request,MotanAdapterGlobalConfig.getMotanInterfaceGroupAndVersionEnabled()); } } public static String getInterfaceName(Caller caller) { return getInterfaceName(caller, false); } public static String getInterfaceName(Caller caller, Boolean useGroupAndVersion) { return useGroupAndVersion ? caller.getUrl().getApplication() : caller.getInterface().getName(); } public static String getInterfaceName(Caller caller, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) .append(getInterfaceName(caller, MotanAdapterGlobalConfig.getMotanInterfaceGroupAndVersionEnabled())) .toString(); } else { return getInterfaceName(caller, MotanAdapterGlobalConfig.getMotanInterfaceGroupAndVersionEnabled()); } } } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/SentinelMotanConsumerFilter.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.motan.config.MotanAdapterGlobalConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.weibo.api.motan.common.MotanConstants; import com.weibo.api.motan.core.extension.Activation; import com.weibo.api.motan.core.extension.SpiMeta; import com.weibo.api.motan.exception.MotanAbstractException; import com.weibo.api.motan.filter.Filter; import com.weibo.api.motan.rpc.Caller; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.Response; /** * @author zhangxn8 */ @Activation(key = MotanConstants.NODE_TYPE_REFERER) @SpiMeta(name = MotanAdapterGlobalConfig.SENTINEL_MOTAN_CONSUMER) public class SentinelMotanConsumerFilter implements Filter { public SentinelMotanConsumerFilter(){ RecordLog.info("Sentinel motan consumer filter initialized"); } @Override public Response filter(Caller caller, Request request) { Entry interfaceEntry = null; Entry methodEntry = null; String prefix = MotanAdapterGlobalConfig.getMotanConsumerPrefix(); String interfaceResourceName = MotanUtils.getInterfaceName(caller, prefix); String methodResourceName = MotanUtils.getMethodResourceName(caller, request, prefix); try { interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, request.getArguments()); Response result = caller.call(request); if (result.getException() != null) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return MotanAdapterGlobalConfig.getConsumerFallback().handle(caller, request, e); } catch (MotanAbstractException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, request.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } } } } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/SentinelMotanProviderFilter.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.motan.config.MotanAdapterGlobalConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.weibo.api.motan.common.MotanConstants; import com.weibo.api.motan.core.extension.Activation; import com.weibo.api.motan.core.extension.SpiMeta; import com.weibo.api.motan.exception.MotanAbstractException; import com.weibo.api.motan.filter.Filter; import com.weibo.api.motan.rpc.Caller; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.Response; import java.util.Map; /** * @author zhangxn8 */ @Activation(key = MotanConstants.NODE_TYPE_SERVICE) @SpiMeta(name = MotanAdapterGlobalConfig.SENTINEL_MOTAN_PROVIDER) public class SentinelMotanProviderFilter implements Filter { public SentinelMotanProviderFilter(){ RecordLog.info("Sentinel motan provider filter initialized"); } @Override public Response filter(Caller caller, Request request) { Entry interfaceEntry = null; Entry methodEntry = null; Map attachment = request.getAttachments(); String origin = attachment.getOrDefault(MotanAdapterGlobalConfig.APPLICATION, MotanAdapterGlobalConfig.MOTAN); String prefix = MotanAdapterGlobalConfig.getMotanProviderPrefix(); String interfaceResourceName = MotanUtils.getInterfaceName(caller, prefix); String methodResourceName = MotanUtils.getMethodResourceName(caller, request, prefix); try { ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, request.getArguments()); Response result = caller.call(request); if (result.getException() != null) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return MotanAdapterGlobalConfig.getProviderFallback().handle(caller, request, e); } catch (MotanAbstractException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, request.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/config/MotanAdapterGlobalConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan.config; import com.alibaba.csp.sentinel.adapter.motan.fallback.DefaultMotanFallback; import com.alibaba.csp.sentinel.adapter.motan.fallback.MotanFallback; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author zhangxn8 */ public class MotanAdapterGlobalConfig { private static final String TRUE_STR = "true"; public static final String APPLICATION = "application"; public static final String MOTAN = "motan"; public static final String BASE_SENTINEL_MOTAN_FILTER = "baseSentinelMotanFilter"; public static final String MOTAN_APP_CONTEXT = "motanAppContext"; public static final String SENTINEL_MOTAN_CONSUMER = "sentinelMotanConsumer"; public static final String SENTINEL_MOTAN_PROVIDER = "sentinelMotanProvider"; public static final String MOTAN_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.motan.resource.use.prefix"; public static final String MOTAN_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.motan.resource.provider.prefix"; public static final String MOTAN_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.motan.resource.consumer.prefix"; public static final String MOTAN_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.motan.interface.group.version.enabled"; private static final String DEFAULT_MOTAN_PROVIDER_PREFIX = "motan:provider:"; private static final String DEFAULT_MOTAN_CONSUMER_PREFIX = "motan:consumer:"; private static volatile MotanFallback consumerFallback = new DefaultMotanFallback(); private static volatile MotanFallback providerFallback = new DefaultMotanFallback(); private MotanAdapterGlobalConfig() {} public static boolean isUsePrefix() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(MOTAN_RES_NAME_WITH_PREFIX_KEY)); } public static String getMotanProviderPrefix() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(MOTAN_PROVIDER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_MOTAN_PROVIDER_PREFIX; } return null; } public static String getMotanConsumerPrefix() { if (isUsePrefix()) { String config = SentinelConfig.getConfig(MOTAN_CONSUMER_RES_NAME_PREFIX_KEY); return StringUtil.isNotBlank(config) ? config : DEFAULT_MOTAN_CONSUMER_PREFIX; } return null; } public static Boolean getMotanInterfaceGroupAndVersionEnabled() { return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(MOTAN_INTERFACE_GROUP_VERSION_ENABLED)); } public static MotanFallback getConsumerFallback() { return consumerFallback; } public static void setConsumerFallback(MotanFallback consumerFallback) { AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); MotanAdapterGlobalConfig.consumerFallback = consumerFallback; } public static MotanFallback getProviderFallback() { return providerFallback; } public static void setProviderFallback(MotanFallback providerFallback) { AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); MotanAdapterGlobalConfig.providerFallback = providerFallback; } } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/fallback/DefaultMotanFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.weibo.api.motan.rpc.Caller; import com.weibo.api.motan.rpc.DefaultResponse; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.Response; /** * @author zhangxn8 */ public class DefaultMotanFallback implements MotanFallback{ @Override public Response handle(Caller caller, Request request, BlockException ex) { DefaultResponse defaultResponse = new DefaultResponse(); defaultResponse.setException(ex.toRuntimeException()); defaultResponse.setRequestId(request.getRequestId()); defaultResponse.setAttachments(request.getAttachments()); defaultResponse.setRpcProtocolVersion(request.getRpcProtocolVersion()); return defaultResponse; } } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/motan/fallback/MotanFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.motan.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.weibo.api.motan.rpc.Caller; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.Response; /** * @author zhangxn8 */ public interface MotanFallback { /** * Handle the block exception and provide fallback result. * @param caller * @param request * @param ex * @return */ Response handle(Caller caller, Request request, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-motan-adapter/src/main/resources/META-INF/services/com.weibo.api.motan.filter.Filter ================================================ com.alibaba.csp.sentinel.adapter.motan.SentinelMotanProviderFilter com.alibaba.csp.sentinel.adapter.motan.SentinelMotanConsumerFilter ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/README.md ================================================ # Sentinel OkHttp Adapter ## Introduction Sentinel provides integration for OkHttp client to enable flow control for web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-okhttp-adapter x.y.z ``` We can add the `SentinelOkHttpInterceptor` interceptor when `OkHttpClient` at initialization, for example: ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SentinelOkHttpInterceptor(new SentinelOkHttpConfig())) .build(); ``` ## Configuration `SentinelOkHttpConfig` configuration: |name|description|type|default value||------|------------|------|-------||resourcePrefix|customized resource name prefix|`String`|`okhttp:`||resourceExtractor|customized resource extractor|`OkHttpResourceExtractor`|`DefaultOkHttpResourceExtractor`||fallback|handle request when it is blocked|`OkHttpFallback`|`DefaultOkHttpFallback`| ### Resource Extractor We can define `OkHttpResourceExtractor` to customize the logic of extracting resource name from the HTTP request. For example: `okhttp:GET:ip:port/okhttp/back/1 ==> /okhttp/back/{id}` ```java OkHttpResourceExtractor extractor = (request, connection) -> { String resource = request.url().toString(); String regex = "/okhttp/back/"; if (resource.contains(regex)) { resource = resource.substring(0, resource.indexOf(regex) + regex.length()) + "{id}"; } return resource; }; ``` The pattern of default resource name extractor is `${HTTP_METHOD}:${URL}` (e.g. `GET:/foo`). ### Fallback (Block handling) We can define `OkHttpFallback` to handle blocked request. For example: ```java public class DefaultOkHttpFallback implements OkHttpFallback { @Override public Response handle(Request request, Connection connection, BlockException e) { return new Response(myErrorBuilder); } } ``` ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-okhttp-adapter jar 3.6.0 2.1.3.RELEASE 5.1.5.RELEASE com.alibaba.csp sentinel-core com.squareup.okhttp3 okhttp ${okhttp.version} provided junit junit test org.mockito mockito-core test com.alibaba fastjson test org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework.boot spring-boot-test ${spring.boot.version} test org.springframework spring-test ${spring-test.version} test ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp; import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor; import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor; import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; import com.alibaba.csp.sentinel.adapter.okhttp.fallback.OkHttpFallback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author zhaoyuguang * @author Eric Zhao */ public class SentinelOkHttpConfig { public static final String DEFAULT_RESOURCE_PREFIX = "okhttp:"; private final String resourcePrefix; private final OkHttpResourceExtractor resourceExtractor; private final OkHttpFallback fallback; public SentinelOkHttpConfig() { this(DEFAULT_RESOURCE_PREFIX); } public SentinelOkHttpConfig(String resourcePrefix) { this(resourcePrefix, new DefaultOkHttpResourceExtractor(), new DefaultOkHttpFallback()); } public SentinelOkHttpConfig(OkHttpResourceExtractor resourceExtractor, OkHttpFallback fallback) { this(DEFAULT_RESOURCE_PREFIX, resourceExtractor, fallback); } public SentinelOkHttpConfig(String resourcePrefix, OkHttpResourceExtractor resourceExtractor, OkHttpFallback fallback) { AssertUtil.notNull(resourceExtractor, "resourceExtractor cannot be null"); AssertUtil.notNull(fallback, "fallback cannot be null"); this.resourcePrefix = resourcePrefix; this.resourceExtractor = resourceExtractor; this.fallback = fallback; } public String getResourcePrefix() { return resourcePrefix; } public OkHttpResourceExtractor getResourceExtractor() { return resourceExtractor; } public OkHttpFallback getFallback() { return fallback; } @Override public String toString() { return "SentinelOkHttpConfig{" + "resourcePrefix='" + resourcePrefix + '\'' + ", resourceExtractor=" + resourceExtractor + ", fallback=" + fallback + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * @author zhaoyuguang */ public class SentinelOkHttpInterceptor implements Interceptor { private final SentinelOkHttpConfig config; public SentinelOkHttpInterceptor() { this.config = new SentinelOkHttpConfig(); } public SentinelOkHttpInterceptor(SentinelOkHttpConfig config) { AssertUtil.notNull(config, "config cannot be null"); this.config = config; } @Override public Response intercept(Chain chain) throws IOException { Entry entry = null; try { Request request = chain.request(); String name = config.getResourceExtractor().extract(request, chain.connection()); if (StringUtil.isNotBlank(config.getResourcePrefix())) { name = config.getResourcePrefix() + name; } entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); return chain.proceed(request); } catch (BlockException e) { return config.getFallback().handle(chain.request(), chain.connection(), e); } catch (IOException ex) { Tracer.traceEntry(ex, entry); throw ex; } finally { if (entry != null) { entry.exit(); } } } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/DefaultOkHttpResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.extractor; import okhttp3.Connection; import okhttp3.Request; /** * @author zhaoyuguang */ public class DefaultOkHttpResourceExtractor implements OkHttpResourceExtractor { @Override public String extract(Request request, Connection connection) { return request.method() + ":" + request.url().toString(); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.extractor; import okhttp3.Connection; import okhttp3.Request; /** * @author zhaoyuguang */ public interface OkHttpResourceExtractor { /** * Extracts the resource name from the HTTP request. * * @param request HTTP request entity * @param connection HTTP connection * @return the resource name of current request */ String extract(Request request, Connection connection); } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/DefaultOkHttpFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import okhttp3.Connection; import okhttp3.Request; import okhttp3.Response; /** * @author zhaoyuguang */ public class DefaultOkHttpFallback implements OkHttpFallback { @Override public Response handle(Request request, Connection connection, BlockException e) { // Just wrap and throw the exception. throw new SentinelRpcException(e); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import okhttp3.Connection; import okhttp3.Request; import okhttp3.Response; /** * @author zhaoyuguang */ public interface OkHttpFallback { Response handle(Request request, Connection connection, BlockException e); } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptorTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.okhttp.app.TestApplication; import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor; import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import okhttp3.Connection; import okhttp3.OkHttpClient; import okhttp3.Request; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertNotNull; /** * @author zhaoyuguang */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = { "server.port=8086" }) public class SentinelOkHttpInterceptorTest { @Value("${server.port}") private Integer port; @Test public void testSentinelOkHttpInterceptor0() throws Exception { // With prefix SentinelOkHttpConfig config = new SentinelOkHttpConfig("okhttp:"); String url0 = "http://localhost:" + port + "/okhttp/back"; OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SentinelOkHttpInterceptor(config)) .build(); Request request = new Request.Builder() .url(url0) .build(); System.out.println(client.newCall(request).execute().body().string()); ClusterNode cn = ClusterBuilderSlot.getClusterNode(config.getResourcePrefix() + "GET:" + url0); assertNotNull(cn); Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); } @Test public void testSentinelOkHttpInterceptor1() throws Exception { String url0 = "http://localhost:" + port + "/okhttp/back/1"; SentinelOkHttpConfig config = new SentinelOkHttpConfig(new OkHttpResourceExtractor() { @Override public String extract(Request request, Connection connection) { String regex = "/okhttp/back/"; String url = request.url().toString(); if (url.contains(regex)) { url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; } return request.method() + ":" + url; } }, new DefaultOkHttpFallback()); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SentinelOkHttpInterceptor(config)) .build(); Request request = new Request.Builder() .url(url0) .build(); System.out.println(client.newCall(request).execute().body().string()); String url1 = config.getResourcePrefix() + "GET:http://localhost:" + port + "/okhttp/back/{id}"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(url1); assertNotNull(cn); Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/TestApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author zhaoyuguang */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/controller/TestController.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.app.controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhaoyuguang */ @RestController public class TestController { @RequestMapping("/okhttp/back") public String back() { return "Welcome Back!"; } @RequestMapping("/okhttp/back/{id}") public String back(@PathVariable String id) { return "Welcome Back! " + id; } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/config/SentinelOkHttpConfigTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.config; import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpConfig; import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor; import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; import org.junit.Test; /** * @author zhaoyuguang */ public class SentinelOkHttpConfigTest { @Test(expected = IllegalArgumentException.class) public void testConfigSetCleaner() { SentinelOkHttpConfig config = new SentinelOkHttpConfig(null, new DefaultOkHttpFallback()); } @Test(expected = IllegalArgumentException.class) public void testConfigSetFallback() { SentinelOkHttpConfig config = new SentinelOkHttpConfig(new DefaultOkHttpResourceExtractor(), null); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractorTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.extractor; import okhttp3.Connection; import okhttp3.Request; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * @author zhaoyuguang */ public class OkHttpResourceExtractorTest { @Test public void testDefaultOkHttpResourceExtractor() { OkHttpResourceExtractor extractor = new DefaultOkHttpResourceExtractor(); String url = "http://localhost:8083/okhttp/back"; Request request = new Request.Builder() .url(url) .build(); String resource = extractor.extract(request, null); assertEquals("GET:" + url, resource); } @Test public void testCustomizeOkHttpUrlCleaner() { OkHttpResourceExtractor extractor = new OkHttpResourceExtractor() { @Override public String extract(Request request, Connection connection) { String regex = "/okhttp/back/"; String url = request.url().toString(); if (url.contains(regex)) { url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; } return request.method() + ":" + url; } }; String url = "http://localhost:8083/okhttp/back/abc"; Request request = new Request.Builder() .url(url) .build(); assertEquals("GET:http://localhost:8083/okhttp/back/{id}", extractor.extract(request, null)); } } ================================================ FILE: sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallbackTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.okhttp.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Test; /** * @author zhaoyuguang */ public class OkHttpFallbackTest { @Test(expected = SentinelRpcException.class) public void testDefaultOkHttpFallback() { BlockException e = new FlowException("xxx"); OkHttpFallback fallback = new DefaultOkHttpFallback(); fallback.handle(null, null, e); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/README.md ================================================ # Sentinel Quarkus Adapter Sentinel provides `sentinel-annotation-quarkus-adapter` and `sentinel-jax-rs-quarkus-adapter` to adapt [sentinel-annotation-cdi-interceptor](https://github.com/alibaba/Sentinel/tree/master/sentinel-extension/sentiel-annotation-cdi-interceptor) and [sentinel-jax-rs-adapter](https://github.com/alibaba/Sentinel/tree/master/sentinel-adapter/sentinel-jax-rs-adapter) for Quarkus. The integration module also provides `sentinel-native-image-quarkus-adapter` to support running Sentinel with Quarkus in native image mode. To use sentinel-jax-rs-quarkus-adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-jax-rs-quarkus-adapter x.y.z ``` To use sentinel-annotation-quarkus-adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-annotation-quarkus-adapter x.y.z ``` When Quarkus application started, you can see the enabled feature like: ```plaintext INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, sentinel-annotation, sentinel-jax-rs] ``` ## For Quarkus native image If you want to integrate Quarkus with Sentinel while running in native image mode, you should add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-native-image-quarkus-adapter x.y.z ``` And then add `--allow-incomplete-classpath` to `quarkus.native.additional-build-args`. If you're using `sentinel-jax-rs-quarkus-adapter`, you'll need to set `quarkus.native.auto-service-loader-registration` to true. When Quarkus application started, you can see the enabled feature like: ```plaintext INFO [io.quarkus] (main) Installed features: [cdi, resteasy, sentinel-annotation, sentinel-jax-rs, sentinel-native-image] ``` For more details you may refer to the `pom.xml` of [sentinel-demo-quarkus](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-quarkus). ### Limitations `sentinel-native-image-quarkus-adapter` currently relies on `sentinel-logging-slf4j` to help Sentinel run in native image mode easily, because `quarkus-core` provides `Target_org_slf4j_LoggerFactory` to substitute `getLogger` method. Currently `sentinel-transport-simple-http` can work in native image mode, while `sentinel-transport-netty-http` cannot work in native image mode without extra config or substitutions. ## References for build native image or AOT - [Quarkus - Tips for writing native applications](https://quarkus.io/guides/writing-native-applications-tips) - [Quarkus - Class Loading Reference](https://quarkus.io/guides/class-loading-reference) - [SubstrateVM LIMITATIONS](https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md) - [Accessing resources in Substrate VM images](https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md) ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-adapter ${revision} ../pom.xml sentinel-quarkus-adapter-parent pom UTF-8 UTF-8 1.8 1.8 true 1.4.1.Final 3.8.1 sentinel-annotation-quarkus-adapter-deployment sentinel-annotation-quarkus-adapter-runtime sentinel-jax-rs-quarkus-adapter-deployment sentinel-jax-rs-quarkus-adapter-runtime sentinel-native-image-quarkus-adapter-deployment sentinel-native-image-quarkus-adapter-runtime io.quarkus quarkus-bom-deployment ${quarkus.version} pom import org.apache.maven.plugins maven-compiler-plugin ${compiler-plugin.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-annotation-quarkus-adapter-deployment 1.8 1.8 io.quarkus quarkus-core-deployment io.quarkus quarkus-arc-deployment com.alibaba.csp sentinel-annotation-quarkus-adapter ${project.version} io.quarkus quarkus-junit5-internal test org.assertj assertj-core test org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterProcessor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; import java.util.Arrays; import java.util.List; /** * @author sea */ class SentinelAnnotationQuarkusAdapterProcessor { private static final String FEATURE_ANNOTATION = "sentinel-annotation"; @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(FEATURE_ANNOTATION)); } @BuildStep List additionalBeans() { return Arrays.asList( new AdditionalBeanBuildItem(SentinelResourceInterceptor.class) ); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding; import com.alibaba.csp.sentinel.slots.block.BlockException; import javax.enterprise.context.ApplicationScoped; import java.util.concurrent.ThreadLocalRandom; /** * @author Eric Zhao * @author sea */ @ApplicationScoped public class FooService { @SentinelResourceBinding(value = "apiFoo", blockHandler = "fooBlockHandler", exceptionsToTrace = {IllegalArgumentException.class}) public String foo(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResourceBinding(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc", exceptionsToTrace = {IllegalArgumentException.class}) public String fooWithFallback(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResourceBinding(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback", fallbackClass = {FooUtil.class}) public String anotherFoo(int i) { if (i == 5758) { throw new IllegalArgumentException("oops"); } return "Hello for " + i; } @SentinelResourceBinding(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class) public int random() { return ThreadLocalRandom.current().nextInt(0, 30000); } @SentinelResourceBinding(value = "apiBaz", blockHandler = "bazBlockHandler", exceptionsToIgnore = {IllegalMonitorStateException.class}) public String baz(String name) { if (name.equals("fail")) { throw new IllegalMonitorStateException("boom!"); } return "cheers, " + name; } public String fooBlockHandler(int i, BlockException ex) { return "Oops, " + i; } public String fooFallbackFunc(int i) { return "eee..."; } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooUtil.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Eric Zhao */ public class FooUtil { public static final int BLOCK_FLAG = 88888; public static final String FALLBACK_DEFAULT_RESULT = "fallback"; public static int globalBlockHandler(BlockException ex) { System.out.println("Oops: " + ex.getClass().getSimpleName()); return BLOCK_FLAG; } public static String globalDefaultFallback(Throwable t) { System.out.println("Fallback caught: " + t.getClass().getSimpleName()); return FALLBACK_DEFAULT_RESULT; } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.MethodUtil; import io.quarkus.arc.ArcUndeclaredThrowableException; import io.quarkus.test.QuarkusUnitTest; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import javax.inject.Inject; import java.util.ArrayList; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author sea */ public class SentinelAnnotationQuarkusAdapterTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap .create(JavaArchive.class) .addClasses(FooService.class, FooUtil.class) .addPackage("com.alibaba.csp.sentinel.annotation.cdi.interceptor") ); @Inject FooService fooService; @BeforeEach public void setUp() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); } @AfterEach public void tearDown() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); } @Test public void testForeignBlockHandlerClass() throws Exception { assertThat(fooService.random()).isNotEqualTo(FooUtil.BLOCK_FLAG); String resourceName = MethodUtil.resolveMethodName(FooService.class.getDeclaredMethod("random")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.random()).isEqualTo(FooUtil.BLOCK_FLAG); assertThat(cn.blockQps()).isPositive(); } @Test public void testBlockHandlerNotFound() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThrows(ArcUndeclaredThrowableException.class, () -> { fooService.baz("Sentinel"); }); } @Test public void testAnnotationExceptionsToIgnore() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); assertThrows(IllegalMonitorStateException.class, () -> { fooService.baz("fail"); }); assertThat(cn.exceptionQps()).isZero(); } @Test public void testFallbackWithNoParams() throws Exception { assertThat(fooService.fooWithFallback(1)).isEqualTo("Hello for 1"); String resourceName = "apiFooWithFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Fallback should be ignored for this. try { fooService.fooWithFallback(5758); fail("should not reach here"); } catch (IllegalAccessException e) { assertThat(cn.exceptionQps()).isZero(); } // Fallback should take effect. assertThat(fooService.fooWithFallback(5763)).isEqualTo("eee..."); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Fallback should not take effect for BlockException, as blockHandler is configured. assertThat(fooService.fooWithFallback(2221)).isEqualTo("Oops, 2221"); assertThat(cn.blockQps()).isPositive(); } @Test public void testDefaultFallbackWithSingleParam() { assertThat(fooService.anotherFoo(1)).isEqualTo("Hello for 1"); String resourceName = "apiAnotherFooWithDefaultFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Default fallback should take effect. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Default fallback should also take effect for BlockException. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.blockQps()).isPositive(); } @Test public void testNormalBlockHandlerAndFallback() throws Exception { assertThat(fooService.foo(1)).isEqualTo("Hello for 1"); String resourceName = "apiFoo"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Test for biz exception. try { fooService.foo(5758); fail("should not reach here"); } catch (Exception ex) { // Should not be traced. assertThat(cn.exceptionQps()).isZero(); } try { fooService.foo(5763); fail("should not reach here"); } catch (Exception ex) { assertThat(cn.exceptionQps()).isPositive(); } // Test for blockHandler FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.foo(1121)).isEqualTo("Oops, 1121"); assertThat(cn.blockQps()).isPositive(); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-annotation-quarkus-adapter io.quarkus quarkus-core ${quarkus.version} com.alibaba.csp sentinel-annotation-cdi-interceptor ${project.version} io.quarkus quarkus-bootstrap-maven-plugin ${quarkus.version} extension-descriptor compile ${project.groupId}:${project.artifactId}-deployment:${project.version} org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml ================================================ --- name: "Sentinel annotation CDI extension" metadata: keywords: - "sentinel" - "rate-limiting" - "resiliency" - "circuit-breaker" - "fault-tolerance" categories: - "fault-tolerance" - "cloud" status: "preview" ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-jax-rs-quarkus-adapter-deployment 1.8 1.8 io.quarkus quarkus-core-deployment io.quarkus quarkus-arc-deployment io.quarkus quarkus-resteasy-server-common-deployment com.alibaba.csp sentinel-jax-rs-quarkus-adapter ${project.version} io.quarkus quarkus-junit5-internal test io.quarkus quarkus-resteasy-deployment test io.rest-assured rest-assured test org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterProcessor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; import org.jboss.logging.Logger; /** * @author sea */ class SentinelJaxRsQuarkusAdapterProcessor { private static final Logger logger = Logger.getLogger(SentinelJaxRsQuarkusAdapterProcessor.class); private static final String FEATURE_JAX_RS = "sentinel-jax-rs"; @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(FEATURE_JAX_RS)); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import io.quarkus.test.QuarkusUnitTest; import io.restassured.response.Response; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.MediaType; import java.util.Collections; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.*; /** * @author sea */ public class SentinelJaxRsQuarkusAdapterTest { private static final String HELLO_STR = "Hello!"; @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap .create(JavaArchive.class) .addClasses(TestResource.class)); @AfterEach public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } @Test public void testGetHello() { String url = "/test/hello"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); } @Test public void testAsyncGetHello() { String url = "/test/async-hello"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); } @Test public void testUrlPathParam() { String url = "/test/hello/{name}"; String resourceName = "GET:" + url; String url1 = "/test/hello/abc"; Response response1 = given().get(url1); response1.then().statusCode(200).body(equalTo("Hello abc !")); String url2 = "/test/hello/def"; Response response2 = given().get(url2); response2.then().statusCode(200).body(equalTo("Hello def !")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(2, cn.passQps(), 0.01); assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url1)); assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url2)); } @Test public void testDefaultFallback() { String url = "/test/hello"; String resourceName = "GET:" + url; configureRulesFor(resourceName, 0); Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testCustomFallback() { String url = "/test/hello"; String resourceName = "GET:" + url; SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { @Override public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) .entity("Blocked by Sentinel (flow limiting)") .type(MediaType.APPLICATION_JSON_TYPE) .build(); } @Override public Future fallbackFutureResponse(final String route, final Throwable cause) { return new FutureTask<>(new Callable() { @Override public javax.ws.rs.core.Response call() throws Exception { return fallbackResponse(route, cause); } }); } }); configureRulesFor(resourceName, 0); Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.OK.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); } @Test public void testCustomRequestOriginParser() { String url = "/test/hello"; String resourceName = "GET:" + url; String limitOrigin = "appB"; final String headerName = "X-APP"; configureRulesFor(resourceName, 0, limitOrigin); SentinelJaxRsConfig.setRequestOriginParser(new RequestOriginParser() { @Override public String parseOrigin(ContainerRequestContext request) { String origin = request.getHeaderString(headerName); return origin != null ? origin : ""; } }); Response response = given() .header(headerName, "appA").get(url); response.then().statusCode(200).body(equalTo(HELLO_STR)); Response blockedResp = given() .header(headerName, "appB") .get(url); blockedResp.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) .body(equalTo("Blocked by Sentinel (flow limiting)")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertEquals(1, cn.blockQps(), 0.01); } @Test public void testExceptionMapper() { String url = "/test/ex"; String resourceName = "GET:" + url; Response response = given().get(url); response.then().statusCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).body(equalTo("test exception mapper")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); } private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/TestResource.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; import javax.ws.rs.*; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @author sea */ @Path("/test") public class TestResource { ExecutorService executor = Executors.newFixedThreadPool(5); @Path("/hello") @GET @Produces({ MediaType.APPLICATION_JSON }) public String sayHello() { return "Hello!"; } @Path("/async-hello") @GET @Produces({ MediaType.APPLICATION_JSON }) public void asyncSayHello(@Suspended final AsyncResponse asyncResponse) { executor.submit(new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } asyncResponse.resume("Hello!"); } }); } @Path("/hello/{name}") @GET @Produces({ MediaType.APPLICATION_JSON }) public String sayHelloWithName(@PathParam(value = "name") String name) { return "Hello " + name + " !"; } @Path("/ex") @GET @Produces({ MediaType.APPLICATION_JSON }) public String exception() { throw new RuntimeException("test exception mapper"); } @Path("/400") @GET @Produces({ MediaType.APPLICATION_JSON }) public String badRequest() { throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity("test return 400") .build()); } @Path("/delay/{seconds}") @GET @Produces({ MediaType.APPLICATION_JSON }) public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException { TimeUnit.SECONDS.sleep(seconds); return "finish"; } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-jax-rs-quarkus-adapter io.quarkus quarkus-core ${quarkus.version} com.alibaba.csp sentinel-jax-rs-adapter ${project.version} io.quarkus quarkus-bootstrap-maven-plugin ${quarkus.version} extension-descriptor compile ${project.groupId}:${project.artifactId}-deployment:${project.version} org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml ================================================ --- name: "Sentinel extension for JAX-RS" metadata: keywords: - "sentinel" - "rate-limiting" - "resiliency" - "circuit-breaker" - "fault-tolerance" - "jax-rs" categories: - "fault-tolerance" - "cloud" status: "preview" ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers ================================================ com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsProviderFilter com.alibaba.csp.sentinel.adapter.jaxrs.exception.DefaultExceptionMapper ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-native-image-quarkus-adapter-deployment 1.8 1.8 io.quarkus quarkus-core-deployment io.quarkus quarkus-arc-deployment org.graalvm.nativeimage svm com.alibaba.csp sentinel-native-image-quarkus-adapter ${project.version} org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelNativeImageProcessor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.nativeimage; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.pkg.steps.NativeBuild; import java.util.Arrays; import java.util.List; /** * @author sea */ class SentinelNativeImageProcessor { private static final String FEATURE_NATIVE_IMAGE = "sentinel-native-image"; @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(FEATURE_NATIVE_IMAGE)); } @BuildStep(onlyIf = NativeBuild.class) List runtimeInitializedClasses() { return Arrays.asList( new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.serializer.JodaCodec"), new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.serializer.GuavaCodec"), new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.support.moneta.MonetaCodec"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.Env"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.init.InitExecutor"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.cluster.ClusterStateManager"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.node.metric.MetricTimerListener"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.node.metric.MetricWriter"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.util.TimeUtil"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.eagleeye.StatLogController"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.logger.EagleEyeLogUtil"), new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.eagleeye.EagleEye")); } @BuildStep(onlyIf = NativeBuild.class) ReflectiveClassBuildItem setupSentinelReflectiveClasses() { return new ReflectiveClassBuildItem(true, true, true, DefaultSlotChainBuilder.class.getName()); } @BuildStep(onlyIf = NativeBuild.class) @Record(ExecutionTime.STATIC_INIT) void record(SentinelRecorder recorder) { recorder.init(); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-quarkus-adapter-parent ${revision} ../pom.xml sentinel-native-image-quarkus-adapter io.quarkus quarkus-core ${quarkus.version} org.graalvm.nativeimage svm com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-logging-slf4j ${project.version} io.quarkus quarkus-bootstrap-maven-plugin ${quarkus.version} extension-descriptor compile ${project.groupId}:${project.artifactId}-deployment:${project.version} org.apache.maven.plugins maven-compiler-plugin io.quarkus quarkus-extension-processor ${quarkus.version} ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelRecorder.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.quarkus.nativeimage; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig; import io.quarkus.runtime.annotations.Recorder; /** * @author sea */ @Recorder public class SentinelRecorder { /** * register fastjson serializer deserializer class info */ public void init() { SerializeConfig.getGlobalInstance().getObjectWriter(NodeVo.class); SerializeConfig.getGlobalInstance().getObjectWriter(FlowRule.class); SerializeConfig.getGlobalInstance().getObjectWriter(SystemRule.class); SerializeConfig.getGlobalInstance().getObjectWriter(DegradeRule.class); SerializeConfig.getGlobalInstance().getObjectWriter(AuthorityRule.class); SerializeConfig.getGlobalInstance().getObjectWriter(ParamFlowRule.class); ParserConfig.getGlobalInstance().getDeserializer(NodeVo.class); ParserConfig.getGlobalInstance().getDeserializer(FlowRule.class); ParserConfig.getGlobalInstance().getDeserializer(SystemRule.class); ParserConfig.getGlobalInstance().getDeserializer(DegradeRule.class); ParserConfig.getGlobalInstance().getDeserializer(AuthorityRule.class); ParserConfig.getGlobalInstance().getDeserializer(ParamFlowRule.class); } } ================================================ FILE: sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml ================================================ --- name: "Sentinel native image extension" metadata: keywords: - "sentinel" - "rate-limiting" - "circuit-breaker" - "native-image" - "fault-tolerance" categories: - "cloud" - "fault-tolerance" status: "preview" ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/README.md ================================================ # Sentinel Reactor Adapter Sentinel provides integration module for [Reactor](https://projectreactor.io/). Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-reactor-adapter x.y.z ``` Example: ```java someService.doSomething() // return type: Mono or Flux .transform(new SentinelReactorTransformer<>(resourceName)) // transform here .subscribe(); ``` ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-reactor-adapter 1.8 1.8 3.2.6.RELEASE com.alibaba.csp sentinel-core io.projectreactor reactor-core ${reactor.version} provided junit junit test io.projectreactor reactor-test ${reactor.version} test ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/ContextConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao */ public class ContextConfig { private final String contextName; private final String origin; public ContextConfig(String contextName) { this(contextName, ""); } public ContextConfig(String contextName, String origin) { AssertUtil.assertNotBlank(contextName, "contextName cannot be blank"); this.contextName = contextName; if (StringUtil.isBlank(origin)) { origin = ""; } this.origin = origin; } public String getContextName() { return contextName; } public String getOrigin() { return origin; } @Override public String toString() { return "ContextConfig{" + "contextName='" + contextName + '\'' + ", origin='" + origin + '\'' + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/EntryConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.Arrays; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.5.0 */ public class EntryConfig { private final String resourceName; private final EntryType entryType; private final int resourceType; private final int acquireCount; private final Object[] args; private final ContextConfig contextConfig; public EntryConfig(String resourceName) { this(resourceName, EntryType.OUT); } public EntryConfig(String resourceName, EntryType entryType) { this(resourceName, entryType, null); } public EntryConfig(String resourceName, EntryType entryType, ContextConfig contextConfig) { this(resourceName, entryType, 1, new Object[0], contextConfig); } public EntryConfig(String resourceName, int resourceType, EntryType entryType, ContextConfig contextConfig) { this(resourceName, resourceType, entryType, 1, new Object[0], contextConfig); } public EntryConfig(String resourceName, EntryType entryType, int acquireCount, Object[] args) { this(resourceName, entryType, acquireCount, args, null); } public EntryConfig(String resourceName, EntryType entryType, int acquireCount, Object[] args, ContextConfig contextConfig) { this(resourceName, ResourceTypeConstants.COMMON, entryType, acquireCount, args, contextConfig); } public EntryConfig(String resourceName, int resourceType, EntryType entryType, int acquireCount, Object[] args) { this(resourceName, resourceType, entryType, acquireCount, args, null); } public EntryConfig(String resourceName, int resourceType, EntryType entryType, int acquireCount, Object[] args, ContextConfig contextConfig) { AssertUtil.assertNotBlank(resourceName, "resourceName cannot be blank"); AssertUtil.notNull(entryType, "entryType cannot be null"); AssertUtil.isTrue(acquireCount > 0, "acquireCount should be positive"); this.resourceName = resourceName; this.entryType = entryType; this.resourceType = resourceType; this.acquireCount = acquireCount; this.args = args; // Constructed ContextConfig should be valid here. Null is allowed here. this.contextConfig = contextConfig; } public String getResourceName() { return resourceName; } public EntryType getEntryType() { return entryType; } public int getAcquireCount() { return acquireCount; } public Object[] getArgs() { return args; } public ContextConfig getContextConfig() { return contextConfig; } /** * @since 1.7.0 */ public int getResourceType() { return resourceType; } @Override public String toString() { return "EntryConfig{" + "resourceName='" + resourceName + '\'' + ", entryType=" + entryType + ", resourceType=" + resourceType + ", acquireCount=" + acquireCount + ", args=" + Arrays.toString(args) + ", contextConfig=" + contextConfig + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/FluxSentinelOperator.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import com.alibaba.csp.sentinel.util.AssertUtil; import reactor.core.CoreSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxOperator; /** * @author Eric Zhao * @since 1.5.0 */ public class FluxSentinelOperator extends FluxOperator { private final EntryConfig entryConfig; public FluxSentinelOperator(Flux source, EntryConfig entryConfig) { super(source); AssertUtil.notNull(entryConfig, "entryConfig cannot be null"); this.entryConfig = entryConfig; } @Override public void subscribe(CoreSubscriber actual) { source.subscribe(new SentinelReactorSubscriber<>(entryConfig, actual, false)); } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/InheritableBaseSubscriber.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.Objects; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.Operators; import reactor.core.publisher.SignalType; /** *

* Copied from {@link reactor.core.publisher.BaseSubscriber} of reactor-core, * but allow sub-classes to override {@code onSubscribe}, {@code onNext}, * {@code onError} and {@code onComplete} method for customization. *

*

This base subscriber also provides predicate for {@code onErrorDropped} hook as a workaround for Sentinel.

*/ abstract class InheritableBaseSubscriber implements CoreSubscriber, Subscription, Disposable { volatile Subscription subscription; static AtomicReferenceFieldUpdater S = AtomicReferenceFieldUpdater.newUpdater(InheritableBaseSubscriber.class, Subscription.class, "subscription"); /** * Return current {@link Subscription} * * @return current {@link Subscription} */ protected Subscription upstream() { return subscription; } @Override public boolean isDisposed() { return subscription == Operators.cancelledSubscription(); } /** * {@link Disposable#dispose() Dispose} the {@link Subscription} by * {@link Subscription#cancel() cancelling} it. */ @Override public void dispose() { cancel(); } /** * Hook for further processing of onSubscribe's Subscription. Implement this method * to call {@link #request(long)} as an initial request. Values other than the * unbounded {@code Long.MAX_VALUE} imply that you'll also call request in * {@link #hookOnNext(Object)}. *

Defaults to request unbounded Long.MAX_VALUE as in {@link #requestUnbounded()} * * @param subscription the subscription to optionally process */ protected void hookOnSubscribe(Subscription subscription) { subscription.request(Long.MAX_VALUE); } /** * Hook for processing of onNext values. You can call {@link #request(long)} here * to further request data from the source {@code org.reactivestreams.Publisher} if * the {@link #hookOnSubscribe(Subscription) initial request} wasn't unbounded. *

Defaults to doing nothing. * * @param value the emitted value to process */ protected void hookOnNext(T value) { // NO-OP } /** * Optional hook for completion processing. Defaults to doing nothing. */ protected void hookOnComplete() { // NO-OP } /** * Optional hook for error processing. Default is to call * {@link Exceptions#errorCallbackNotImplemented(Throwable)}. * * @param throwable the error to process */ protected void hookOnError(Throwable throwable) { throw Exceptions.errorCallbackNotImplemented(throwable); } /** * Optional hook executed when the subscription is cancelled by calling this * Subscriber's {@link #cancel()} method. Defaults to doing nothing. */ protected void hookOnCancel() { //NO-OP } /** * Optional hook executed after any of the termination events (onError, onComplete, * cancel). The hook is executed in addition to and after {@link #hookOnError(Throwable)}, * {@link #hookOnComplete()} and {@link #hookOnCancel()} hooks, even if these callbacks * fail. Defaults to doing nothing. A failure of the callback will be caught by * {@code Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. * * @param type the type of termination event that triggered the hook * ({@link SignalType#ON_ERROR}, {@link SignalType#ON_COMPLETE} or * {@link SignalType#CANCEL}) */ protected void hookFinally(SignalType type) { //NO-OP } @Override public void onSubscribe(Subscription s) { if (Operators.setOnce(S, this, s)) { try { hookOnSubscribe(s); } catch (Throwable throwable) { onError(Operators.onOperatorError(s, throwable, currentContext())); } } } @Override public void onNext(T value) { Objects.requireNonNull(value, "onNext"); try { hookOnNext(value); } catch (Throwable throwable) { onError(Operators.onOperatorError(subscription, throwable, value, currentContext())); } } protected boolean shouldCallErrorDropHook() { return true; } @Override public void onError(Throwable t) { Objects.requireNonNull(t, "onError"); if (S.getAndSet(this, Operators.cancelledSubscription()) == Operators .cancelledSubscription()) { // Already cancelled concurrently // Workaround for Sentinel BlockException: // Here we add a predicate method to decide whether exception should be dropped implicitly // or call the {@code onErrorDropped} hook. if (shouldCallErrorDropHook()) { Operators.onErrorDropped(t, currentContext()); } return; } try { hookOnError(t); } catch (Throwable e) { e = Exceptions.addSuppressed(e, t); Operators.onErrorDropped(e, currentContext()); } finally { safeHookFinally(SignalType.ON_ERROR); } } @Override public void onComplete() { if (S.getAndSet(this, Operators.cancelledSubscription()) != Operators .cancelledSubscription()) { //we're sure it has not been concurrently cancelled try { hookOnComplete(); } catch (Throwable throwable) { //onError itself will short-circuit due to the CancelledSubscription being push above hookOnError(Operators.onOperatorError(throwable, currentContext())); } finally { safeHookFinally(SignalType.ON_COMPLETE); } } } @Override public final void request(long n) { if (Operators.validate(n)) { Subscription s = this.subscription; if (s != null) { s.request(n); } } } /** * {@link #request(long) Request} an unbounded amount. */ public final void requestUnbounded() { request(Long.MAX_VALUE); } @Override public final void cancel() { if (Operators.terminate(S, this)) { try { hookOnCancel(); } catch (Throwable throwable) { hookOnError(Operators.onOperatorError(subscription, throwable, currentContext())); } finally { safeHookFinally(SignalType.CANCEL); } } } void safeHookFinally(SignalType type) { try { hookFinally(type); } catch (Throwable finallyFailure) { Operators.onErrorDropped(finallyFailure, currentContext()); } } @Override public String toString() { return getClass().getSimpleName(); } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/MonoSentinelOperator.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import com.alibaba.csp.sentinel.util.AssertUtil; import reactor.core.CoreSubscriber; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoOperator; /** * @author Eric Zhao * @since 1.5.0 */ public class MonoSentinelOperator extends MonoOperator { private final EntryConfig entryConfig; public MonoSentinelOperator(Mono source, EntryConfig entryConfig) { super(source); AssertUtil.notNull(entryConfig, "entryConfig cannot be null"); this.entryConfig = entryConfig; } @Override public void subscribe(CoreSubscriber actual) { source.subscribe(new SentinelReactorSubscriber<>(entryConfig, actual, true)); } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/ReactorSphU.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.concurrent.atomic.AtomicReference; import com.alibaba.csp.sentinel.AsyncEntry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slots.block.BlockException; import reactor.core.publisher.Mono; /** * A {@link SphU} adapter with Project Reactor. * * @author Eric Zhao * @since 1.5.0 */ public final class ReactorSphU { public static Mono entryWith(String resourceName, Mono actual) { return entryWith(resourceName, EntryType.OUT, actual); } public static Mono entryWith(String resourceName, EntryType entryType, Mono actual) { final AtomicReference entryWrapper = new AtomicReference<>(null); return Mono.defer(() -> { try { AsyncEntry entry = SphU.asyncEntry(resourceName, entryType); entryWrapper.set(entry); return actual.subscriberContext(context -> { if (entry == null) { return context; } Context sentinelContext = entry.getAsyncContext(); if (sentinelContext == null) { return context; } // TODO: check GC friendly? return context.put(SentinelReactorConstants.SENTINEL_CONTEXT_KEY, sentinelContext); }).doOnSuccessOrError((o, t) -> { if (entry != null && entryWrapper.compareAndSet(entry, null)) { if (t != null) { Tracer.traceContext(t, 1, entry.getAsyncContext()); } entry.exit(); } }); } catch (BlockException ex) { return Mono.error(ex); } }); } private ReactorSphU() {} } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/SentinelReactorConstants.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; /** * @author Eric Zhao * @since 1.5.0 */ public final class SentinelReactorConstants { public static final String SENTINEL_CONTEXT_KEY = "_sentinel_context"; private SentinelReactorConstants() {} } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/SentinelReactorSubscriber.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.AsyncEntry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Supplier; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.util.context.Context; /** * @author Eric Zhao * @since 1.5.0 */ public class SentinelReactorSubscriber extends InheritableBaseSubscriber { private final EntryConfig entryConfig; private final CoreSubscriber actual; private final boolean unary; private volatile AsyncEntry currentEntry; private final AtomicBoolean entryExited = new AtomicBoolean(false); public SentinelReactorSubscriber(EntryConfig entryConfig, CoreSubscriber actual, boolean unary) { checkEntryConfig(entryConfig); this.entryConfig = entryConfig; this.actual = actual; this.unary = unary; } private void checkEntryConfig(EntryConfig config) { AssertUtil.notNull(config, "entryConfig cannot be null"); } @Override public Context currentContext() { if (currentEntry == null || entryExited.get()) { return actual.currentContext(); } com.alibaba.csp.sentinel.context.Context sentinelContext = currentEntry.getAsyncContext(); if (sentinelContext == null) { return actual.currentContext(); } return actual.currentContext() .put(SentinelReactorConstants.SENTINEL_CONTEXT_KEY, currentEntry.getAsyncContext()); } private void doWithContextOrCurrent(Supplier> contextSupplier, Runnable f) { Optional contextOpt = contextSupplier.get(); if (!contextOpt.isPresent()) { // Provided context is absent, use current context. f.run(); } else { // Run on provided context. ContextUtil.runOnContext(contextOpt.get(), f); } } private void entryWhenSubscribed() { ContextConfig sentinelContextConfig = entryConfig.getContextConfig(); if (sentinelContextConfig != null) { // If current we're already in a context, the context config won't work. ContextUtil.enter(sentinelContextConfig.getContextName(), sentinelContextConfig.getOrigin()); } try { AsyncEntry entry = SphU.asyncEntry(entryConfig.getResourceName(), entryConfig.getResourceType(), entryConfig.getEntryType(), entryConfig.getAcquireCount(), entryConfig.getArgs()); this.currentEntry = entry; actual.onSubscribe(this); } catch (BlockException ex) { // Mark as completed (exited) explicitly. entryExited.set(true); // Signal cancel and propagate the {@code BlockException}. cancel(); actual.onSubscribe(this); actual.onError(ex); } finally { if (sentinelContextConfig != null) { ContextUtil.exit(); } } } @Override protected void hookOnSubscribe(Subscription subscription) { doWithContextOrCurrent(() -> currentContext().getOrEmpty(SentinelReactorConstants.SENTINEL_CONTEXT_KEY), this::entryWhenSubscribed); } @Override protected void hookOnNext(T value) { if (isDisposed()) { tryCompleteEntry(); return; } doWithContextOrCurrent(() -> Optional.ofNullable(currentEntry).map(AsyncEntry::getAsyncContext), () -> actual.onNext(value)); if (unary) { // For some cases of unary operator (Mono), we have to do this during onNext hook. // e.g. this kind of order: onSubscribe() -> onNext() -> cancel() -> onComplete() // the onComplete hook will not be executed so we'll need to complete the entry in advance. tryCompleteEntry(); } } @Override protected void hookOnComplete() { tryCompleteEntry(); actual.onComplete(); } @Override protected boolean shouldCallErrorDropHook() { // When flow control triggered or stream terminated, the incoming // deprecated exceptions should be dropped implicitly, so we'll not call the `onErrorDropped` hook. return !entryExited.get(); } @Override protected void hookOnError(Throwable t) { if (currentEntry != null && currentEntry.getAsyncContext() != null) { // Normal requests with non-BlockException will go through here. Tracer.traceContext(t, 1, currentEntry.getAsyncContext()); } tryCompleteEntry(); actual.onError(t); } @Override protected void hookOnCancel() { tryCompleteEntry(); } private boolean tryCompleteEntry() { if (currentEntry != null && entryExited.compareAndSet(false, true)) { currentEntry.exit(1, entryConfig.getArgs()); return true; } return false; } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/reactor/SentinelReactorTransformer.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.function.Function; import com.alibaba.csp.sentinel.util.AssertUtil; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * A transformer that transforms given {@code Publisher} to a wrapped Sentinel reactor operator. * * @author Eric Zhao * @since 1.5.0 */ public class SentinelReactorTransformer implements Function, Publisher> { private final EntryConfig entryConfig; public SentinelReactorTransformer(String resourceName) { this(new EntryConfig(resourceName)); } public SentinelReactorTransformer(EntryConfig entryConfig) { AssertUtil.notNull(entryConfig, "entryConfig cannot be null"); this.entryConfig = entryConfig; } @Override public Publisher apply(Publisher publisher) { if (publisher instanceof Mono) { return new MonoSentinelOperator<>((Mono) publisher, entryConfig); } if (publisher instanceof Flux) { return new FluxSentinelOperator<>((Flux) publisher, entryConfig); } throw new IllegalStateException("Publisher type is not supported: " + publisher.getClass().getCanonicalName()); } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/reactor/FluxSentinelOperatorTestIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.ArrayList; import java.util.Collections; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class FluxSentinelOperatorTestIntegrationTest { @Test public void testEmitMultipleValueSuccess() { String resourceName = createResourceName("testEmitMultipleSuccess"); StepVerifier.create(Flux.just(1, 2) .map(e -> e * 2) .transform(new SentinelReactorTransformer<>(resourceName))) .expectNext(2) .expectNext(4) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testEmitFluxError() { String resourceName = createResourceName("testEmitFluxError"); StepVerifier.create(Flux.error(new IllegalAccessException("oops")) .transform(new SentinelReactorTransformer<>(resourceName))) .expectError(IllegalAccessException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertEquals(1, cn.totalException()); } @Test public void testEmitMultipleValuesWhenFlowControlTriggered() { String resourceName = createResourceName("testEmitMultipleValuesWhenFlowControlTriggered"); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); StepVerifier.create(Flux.just(1, 3, 5) .map(e -> e * 2) .transform(new SentinelReactorTransformer<>(resourceName))) .expectError(BlockException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest()); FlowRuleManager.loadRules(new ArrayList<>()); } private String createResourceName(String resourceName) { return "reactor_test_flux_" + resourceName; } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/reactor/MonoSentinelOperatorIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.adapter.reactor; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class MonoSentinelOperatorIntegrationTest { @Test public void testTransformMonoWithSentinelContextEnter() { String resourceName = createResourceName("testTransformMonoWithSentinelContextEnter"); String contextName = "test_reactive_context"; String origin = "originA"; FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0).setLimitApp(origin).as(FlowRule.class) )); StepVerifier.create(Mono.just(2) .transform(new SentinelReactorTransformer<>( // Customized context with origin. new EntryConfig(resourceName, EntryType.OUT, new ContextConfig(contextName, origin)))) ) .expectError(BlockException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest()); assertTrue(Constants.ROOT.getChildList() .stream() .filter(node -> node instanceof EntranceNode) .map(e -> (EntranceNode)e) .anyMatch(e -> e.getId().getName().equals(contextName)) ); FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testFluxToMonoNextThenCancelSuccess() { String resourceName = createResourceName("testFluxToMonoNextThenCancelSuccess"); StepVerifier.create(Flux.range(1, 10) .map(e -> e * 2) .next() .transform(new SentinelReactorTransformer<>(resourceName))) .expectNext(2) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testEmitSingleLongTimeRt() { String resourceName = createResourceName("testEmitSingleLongTimeRt"); StepVerifier.create(Mono.just(2) .delayElement(Duration.ofMillis(1000)) .map(e -> e * 2) .transform(new SentinelReactorTransformer<>(resourceName))) .expectNext(4) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1000, cn.avgRt(), 20); } @Test public void testEmitEmptySuccess() { String resourceName = createResourceName("testEmitEmptySuccess"); StepVerifier.create(Mono.empty() .transform(new SentinelReactorTransformer<>(resourceName))) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testEmitSingleSuccess() { String resourceName = createResourceName("testEmitSingleSuccess"); StepVerifier.create(Mono.just(1) .transform(new SentinelReactorTransformer<>(resourceName))) .expectNext(1) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testEmitSingleValueWhenFlowControlTriggered() { String resourceName = createResourceName("testEmitSingleValueWhenFlowControlTriggered"); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); StepVerifier.create(Mono.just(1) .map(e -> e * 2) .transform(new SentinelReactorTransformer<>(resourceName))) .expectError(BlockException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest()); FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testEmitExceptionWhenFlowControlTriggered() { String resourceName = createResourceName("testEmitExceptionWhenFlowControlTriggered"); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); StepVerifier.create(Mono.error(new IllegalStateException("some")) .transform(new SentinelReactorTransformer<>(resourceName))) .expectError(BlockException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest()); FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testEmitSingleError() { String resourceName = createResourceName("testEmitSingleError"); StepVerifier.create(Mono.error(new IllegalStateException()) .transform(new SentinelReactorTransformer<>(resourceName))) .expectError(IllegalStateException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.totalException()); } @Test public void testMultipleReactorTransformerLatterFlowControl() { String resourceName1 = createResourceName("testMultipleReactorTransformerLatterFlowControl1"); String resourceName2 = createResourceName("testMultipleReactorTransformerLatterFlowControl2"); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName2).setCount(0) )); StepVerifier.create(Mono.just(2) .transform(new SentinelReactorTransformer<>(resourceName1)) .transform(new SentinelReactorTransformer<>(resourceName2))) .expectError(BlockException.class) .verify(); ClusterNode cn1 = ClusterBuilderSlot.getClusterNode(resourceName1); assertNotNull(cn1); ClusterNode cn2 = ClusterBuilderSlot.getClusterNode(resourceName2); assertNotNull(cn2); assertEquals(1, cn2.blockRequest()); assertEquals(1, cn1.totalSuccess()); FlowRuleManager.loadRules(new ArrayList<>()); } private String createResourceName(String resourceName) { return "reactor_test_mono_" + resourceName; } } ================================================ FILE: sentinel-adapter/sentinel-reactor-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/reactor/ReactorSphUTest.java ================================================ package com.alibaba.csp.sentinel.adapter.reactor; import java.util.ArrayList; import java.util.Collections; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.junit.Test; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ReactorSphUTest { @Test public void testReactorEntryNormalWhenFlowControlTriggered() { String resourceName = createResourceName("testReactorEntryNormalWhenFlowControlTriggered"); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); StepVerifier.create(ReactorSphU.entryWith(resourceName, Mono.just(60)) .subscribeOn(Schedulers.elastic()) .map(e -> e * 3)) .expectError(BlockException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(0, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest()); FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testReactorEntryWithCommon() { String resourceName = createResourceName("testReactorEntryWithCommon"); StepVerifier.create(ReactorSphU.entryWith(resourceName, Mono.just(60)) .subscribeOn(Schedulers.elastic()) .map(e -> e * 3)) .expectNext(180) .verifyComplete(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testReactorEntryWithBizException() { String resourceName = createResourceName("testReactorEntryWithBizException"); StepVerifier.create(ReactorSphU.entryWith(resourceName, Mono.error(new IllegalStateException()))) .expectError(IllegalStateException.class) .verify(); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertEquals(1, cn.totalException()); } private String createResourceName(String resourceName) { return "reactor_test_SphU_" + resourceName; } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/README.md ================================================ # Sentinel SOFARPC Adapter Sentinel SOFARPC Adapter provides service provider filter and consumer filter for [SOFARPC](https://www.sofastack.tech/projects/sofa-rpc) services. **Note: This adapter supports SOFARPC 5.4.x version and above, and 5.6.x is officially recommended.** To use Sentinel SOFARPC Adapter, you can simply add the following dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-sofa-rpc-adapter x.y.z ``` The Sentinel filters are **enabled by default**. Once you add the dependency, the SOFARPC services and methods will become protected resources in Sentinel, which can leverage Sentinel's flow control and guard ability when rules are configured. Demos can be found in [sentinel-demo-sofa-rpc](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-sofa-rpc). If you don't want the filters enabled, you can manually disable them. For example: ```java providerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); ``` or add setting in `rpc-config.json` file, and its priority is lower than above. ```json { "sofa.rpc.sentinel.enabled": true } ``` For more details of SOFARPC filter, see [SOFARPC filter documentation](https://www.sofastack.tech/projects/sofa-rpc/custom-filter/). ## SOFARPC resources The resource for SOFARPC services has two granularities: service interface and service method. - Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.sofa.rpc.DemoService` - Service method:resourceName format is `interfaceName#methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.sofa.rpc.DemoService#sayHello(java.lang.Integer,java.lang.String,int)` ## Flow control based on caller In many circumstances, it's also significant to control traffic flow based on the **caller**. For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider. If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name). Sentinel SOFARPC Adapter will automatically resolve the SOFARPC consumer's *application name* as the caller's name (`origin`), and will bring the caller's name when doing resource protection. If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers. If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. ## Global fallback Sentinel SOFARPC Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `SofaRpcFallback` interface and then register to `SofaRpcFallbackRegistry`. If no fallback is configured, Sentinel will wrap the `BlockException` then directly throw it out. ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-sofa-rpc-adapter 5.6.4 com.alibaba.csp sentinel-core com.alipay.sofa sofa-rpc-all ${sofa-rpc-all.version} provided junit junit test org.mockito mockito-core test ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/AbstractSofaRpcFilter.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.Tracer; import com.alipay.sofa.rpc.common.RpcConfigs; import com.alipay.sofa.rpc.common.utils.StringUtils; import com.alipay.sofa.rpc.config.AbstractInterfaceConfig; import com.alipay.sofa.rpc.core.exception.RpcErrorType; import com.alipay.sofa.rpc.core.exception.SofaRpcException; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.Filter; import com.alipay.sofa.rpc.filter.FilterInvoker; /** * @author cdfive */ abstract class AbstractSofaRpcFilter extends Filter { @Override public boolean needToLoad(FilterInvoker invoker) { AbstractInterfaceConfig config = invoker.getConfig(); String enabled = config.getParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED); if (StringUtils.isNotBlank(enabled)) { return Boolean.parseBoolean(enabled); } return RpcConfigs.getOrDefaultValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, true); } protected void traceResponseException(SofaResponse response, Entry interfaceEntry, Entry methodEntry) { if (response.isError()) { SofaRpcException rpcException = new SofaRpcException(RpcErrorType.SERVER_FILTER, response.getErrorMsg()); Tracer.traceEntry(rpcException, interfaceEntry); Tracer.traceEntry(rpcException, methodEntry); } else { Object appResponse = response.getAppResponse(); if (appResponse instanceof Throwable) { Tracer.traceEntry((Throwable) appResponse, interfaceEntry); Tracer.traceEntry((Throwable) appResponse, methodEntry); } } } protected SofaRpcException traceOtherException(Throwable t, Entry interfaceEntry, Entry methodEntry) { SofaRpcException rpcException; if (t instanceof SofaRpcException) { rpcException = (SofaRpcException) t; } else { rpcException = new SofaRpcException(RpcErrorType.SERVER_FILTER, t); } Tracer.traceEntry(rpcException, interfaceEntry); Tracer.traceEntry(rpcException, methodEntry); return rpcException; } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SentinelConstants.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; /** * @author cdfive * @since 1.7.2 */ public final class SentinelConstants { public static final String SOFA_RPC_SENTINEL_ENABLED = "sofa.rpc.sentinel.enabled"; private SentinelConstants() {} } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SentinelSofaRpcConsumerFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alipay.sofa.rpc.common.RpcConstants; import com.alipay.sofa.rpc.core.exception.SofaRpcException; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.ext.Extension; import com.alipay.sofa.rpc.filter.AutoActive; import com.alipay.sofa.rpc.filter.FilterInvoker; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getInterfaceResourceName; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodResourceName; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodArguments; /** * SOFARPC service consumer filter for Sentinel, auto activated by default. * * If you want to disable the consumer filter, you can configure: *

ConsumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
* * or add setting in rpc-config.json: *
"sofa.rpc.sentinel.enabled": false 
* * @author cdfive */ @Extension(value = "consumerSentinel", order = -1000) @AutoActive(consumerSide = true) public class SentinelSofaRpcConsumerFilter extends AbstractSofaRpcFilter { @Override public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException { // Now only support sync invoke. if (request.getInvokeType() != null && !RpcConstants.INVOKER_TYPE_SYNC.equals(request.getInvokeType())) { return invoker.invoke(request); } String interfaceResourceName = getInterfaceResourceName(request); String methodResourceName = getMethodResourceName(request); Entry interfaceEntry = null; Entry methodEntry = null; try { interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, getMethodArguments(request)); SofaResponse response = invoker.invoke(request); traceResponseException(response, interfaceEntry, methodEntry); return response; } catch (BlockException e) { return SofaRpcFallbackRegistry.getConsumerFallback().handle(invoker, request, e); } catch (Throwable t) { throw traceOtherException(t, interfaceEntry, methodEntry); } finally { if (methodEntry != null) { methodEntry.exit(1, getMethodArguments(request)); } if (interfaceEntry != null) { interfaceEntry.exit(); } } } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SentinelSofaRpcProviderFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alipay.sofa.rpc.common.RpcConstants; import com.alipay.sofa.rpc.core.exception.SofaRpcException; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.ext.Extension; import com.alipay.sofa.rpc.filter.AutoActive; import com.alipay.sofa.rpc.filter.FilterInvoker; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getApplicationName; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getInterfaceResourceName; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodResourceName; import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodArguments; /** * SOFARPC service provider filter for Sentinel, auto activated by default. * * If you want to disable the provider filter, you can configure: *
ProviderConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
* * or add setting in rpc-config.json file: *
 * {
 *   "sofa.rpc.sentinel.enabled": false
 * }
 * 
* * @author cdfive */ @Extension(value = "providerSentinel", order = -1000) @AutoActive(providerSide = true) public class SentinelSofaRpcProviderFilter extends AbstractSofaRpcFilter { @Override public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException { // Now only support sync invoke. if (request.getInvokeType() != null && !RpcConstants.INVOKER_TYPE_SYNC.equals(request.getInvokeType())) { return invoker.invoke(request); } String callerApp = getApplicationName(request); String interfaceResourceName = getInterfaceResourceName(request); String methodResourceName = getMethodResourceName(request); Entry interfaceEntry = null; Entry methodEntry = null; try { ContextUtil.enter(methodResourceName, callerApp); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, getMethodArguments(request)); SofaResponse response = invoker.invoke(request); traceResponseException(response, interfaceEntry, methodEntry); return response; } catch (BlockException e) { return SofaRpcFallbackRegistry.getProviderFallback().handle(invoker, request, e); } catch (Throwable t) { throw traceOtherException(t, interfaceEntry, methodEntry); } finally { if (methodEntry != null) { methodEntry.exit(1, getMethodArguments(request)); } if (interfaceEntry != null) { interfaceEntry.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SofaRpcUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alipay.sofa.rpc.common.RemotingConstants; import com.alipay.sofa.rpc.core.request.SofaRequest; /** * @author cdfive */ public class SofaRpcUtils { public static String getApplicationName(SofaRequest request) { String appName = (String) request.getRequestProp(RemotingConstants.HEAD_APP_NAME); return appName == null ? "" : appName; } public static String getInterfaceResourceName(SofaRequest request) { return request.getInterfaceName(); } public static String getMethodResourceName(SofaRequest request) { StringBuilder buf = new StringBuilder(64); buf.append(request.getInterfaceName()) .append("#") .append(request.getMethodName()) .append("("); boolean isFirst = true; for (String methodArgSig : request.getMethodArgSigs()) { if (!isFirst) { buf.append(","); } else { isFirst = false; } buf.append(methodArgSig); } buf.append(")"); return buf.toString(); } public static Object[] getMethodArguments(SofaRequest request) { return request.getMethodArgs(); } private SofaRpcUtils() {} } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/fallback/DefaultSofaRpcFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.FilterInvoker; /** * Default Sentinel fallback handler for SOFARPC services. * Just wrap and throw the exception. * * @author cdfive */ public class DefaultSofaRpcFallback implements SofaRpcFallback { @Override public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) { // Just wrap and throw the exception. throw new SentinelRpcException(ex); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/fallback/SofaRpcFallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.FilterInvoker; /** * Sentinel fallback handler for SOFARPC services. * * @author cdfive */ public interface SofaRpcFallback { /** * Handle the block exception and provide fallback result. * * @param invoker FilterInvoker * @param request SofaRequest * @param ex block exception * @return fallback result */ SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/fallback/SofaRpcFallbackRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Global Sentinel fallback registry for SOFARPC services. * * @author cdfive */ public final class SofaRpcFallbackRegistry { private static volatile SofaRpcFallback providerFallback = new DefaultSofaRpcFallback(); private static volatile SofaRpcFallback consumerFallback = new DefaultSofaRpcFallback(); public static SofaRpcFallback getProviderFallback() { return providerFallback; } public static void setProviderFallback(SofaRpcFallback providerFallback) { AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); SofaRpcFallbackRegistry.providerFallback = providerFallback; } public static SofaRpcFallback getConsumerFallback() { return consumerFallback; } public static void setConsumerFallback(SofaRpcFallback consumerFallback) { AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); SofaRpcFallbackRegistry.consumerFallback = consumerFallback; } private SofaRpcFallbackRegistry() {} } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/main/resources/META-INF/services/sofa-rpc/com.alipay.sofa.rpc.filter.Filter ================================================ # name # order com.alibaba.csp.sentinel.adapter.sofa.rpc.SentinelSofaRpcProviderFilter # -1000 com.alibaba.csp.sentinel.adapter.sofa.rpc.SentinelSofaRpcConsumerFilter # -1000 ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/AbstractSofaRpcFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alipay.sofa.rpc.codec.Serializer; import com.alipay.sofa.rpc.common.RpcConfigs; import com.alipay.sofa.rpc.config.ConsumerConfig; import com.alipay.sofa.rpc.config.ProviderConfig; import com.alipay.sofa.rpc.filter.FilterInvoker; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import static org.junit.Assert.*; /** * Test cases for {@link AbstractSofaRpcFilter}. * * @author cdfive */ public class AbstractSofaRpcFilterTest { @Before public void setUp() { removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED); } @After public void cleanUp() { removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED); } @Test public void testNeedToLoadProvider() { SentinelSofaRpcProviderFilter providerFilter = new SentinelSofaRpcProviderFilter(); ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setInterfaceId(Serializer.class.getName()); providerConfig.setId("AAA"); FilterInvoker invoker = new FilterInvoker(null, null, providerConfig); assertTrue(providerFilter.needToLoad(invoker)); providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(providerFilter.needToLoad(invoker)); providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, ""); assertTrue(providerFilter.needToLoad(invoker)); RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(providerFilter.needToLoad(invoker)); } @Test public void testNeedToLoadConsumer() { SentinelSofaRpcConsumerFilter consumerFilter = new SentinelSofaRpcConsumerFilter(); ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setInterfaceId(Serializer.class.getName()); consumerConfig.setId("BBB"); FilterInvoker invoker = new FilterInvoker(null, null, consumerConfig); assertTrue(consumerFilter.needToLoad(invoker)); consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(consumerFilter.needToLoad(invoker)); consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, ""); assertTrue(consumerFilter.needToLoad(invoker)); RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(consumerFilter.needToLoad(invoker)); } @Test public void testNeedToLoadProviderAndConsumer() { SentinelSofaRpcProviderFilter providerFilter = new SentinelSofaRpcProviderFilter(); ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setInterfaceId(Serializer.class.getName()); providerConfig.setId("AAA"); FilterInvoker providerInvoker = new FilterInvoker(null, null, providerConfig); assertTrue(providerFilter.needToLoad(providerInvoker)); SentinelSofaRpcConsumerFilter consumerFilter = new SentinelSofaRpcConsumerFilter(); ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setInterfaceId(Serializer.class.getName()); consumerConfig.setId("BBB"); FilterInvoker consumerInvoker = new FilterInvoker(null, null, consumerConfig); assertTrue(consumerFilter.needToLoad(consumerInvoker)); providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(providerFilter.needToLoad(providerInvoker)); assertTrue(consumerFilter.needToLoad(consumerInvoker)); providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, ""); assertTrue(providerFilter.needToLoad(providerInvoker)); RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false"); assertFalse(providerFilter.needToLoad(providerInvoker)); assertFalse(consumerFilter.needToLoad(consumerInvoker)); } private void removeRpcConfig(String key) { try { Method removeValueMethod = RpcConfigs.class.getDeclaredMethod("removeValue", String.class); removeValueMethod.setAccessible(true); removeValueMethod.invoke(null, key); } catch (Exception e) { // Empty } } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/BaseTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.CtSph; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import java.lang.reflect.Method; /** * Base test class, provide common methods for sub test class. * * Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive */ public class BaseTest { /** * Clean up resources. */ protected static void cleanUpAll() { Context context = ContextUtil.getContext(); if (context != null) { context.setCurEntry(null); ContextUtil.exit(); } Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); // Clear chainMap in CtSph try { Method resetChainMapMethod = CtSph.class.getDeclaredMethod("resetChainMap"); resetChainMapMethod.setAccessible(true); resetChainMapMethod.invoke(null); } catch (Exception e) { // Empty } } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SentinelSofaRpcConsumerFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alipay.sofa.rpc.common.RpcConstants; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.FilterInvoker; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Test cases for {@link SentinelSofaRpcConsumerFilter}. * * @author cdfive */ public class SentinelSofaRpcConsumerFilterTest extends BaseTest { @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvokeSentinelWorks() { SentinelSofaRpcConsumerFilter filter = new SentinelSofaRpcConsumerFilter(); final String interfaceResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService"; final String methodResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)"; SofaRequest request = mock(SofaRequest.class); when(request.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC); when(request.getInterfaceName()).thenReturn(interfaceResourceName); when(request.getMethodName()).thenReturn("sayHello"); when(request.getMethodArgSigs()).thenReturn(new String[]{"java.lang.String", "int"}); when(request.getMethodArgs()).thenReturn(new Object[]{"Sentinel", 2020}); FilterInvoker filterInvoker = mock(FilterInvoker.class); when(filterInvoker.invoke(request)).thenAnswer(new Answer() { @Override public SofaResponse answer(InvocationOnMock invocationOnMock) throws Throwable { verifyInvocationStructure(interfaceResourceName, methodResourceName); SofaResponse response = new SofaResponse(); response.setAppResponse("Hello Sentinel 2020"); return response; } }); // Before invoke assertNull(ContextUtil.getContext()); // Do invoke SofaResponse response = filter.invoke(filterInvoker, request); assertEquals("Hello Sentinel 2020", response.getAppResponse()); verify(filterInvoker).invoke(request); // After invoke, make sure exit context assertNull(ContextUtil.getContext()); } /** * Verify Sentinel invocation structure in memory: * EntranceNode(defaultContextName) * --InterfaceNode(interfaceName) * ----MethodNode(resourceName) */ private void verifyInvocationStructure(String interfaceResourceName, String methodResourceName) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(methodResourceName, applicationName) in SentinelSofaRpcConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by SentinelSofaRpcProviderFilter // If consumer is on the top of SOFARPC invocation chain, use default context assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceResourceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(interfaceResourceName, interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(methodResourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(methodResourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); // As context origin is not "", no originNode should be created in curEntry assertNull(curEntry.getOriginNode()); // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); // Different resource->Different ProcessorSlot->Different ClusterNode assertNotSame(methodClusterNode, interfaceClusterNode); // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(0, methodOriginCountMap.size()); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SentinelSofaRpcProviderFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alipay.sofa.rpc.common.RpcConstants; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.FilterInvoker; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Map; import java.util.Set; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Test cases for {@link SentinelSofaRpcProviderFilter}. * * @author cdfive */ public class SentinelSofaRpcProviderFilterTest extends BaseTest { @Before public void setUp() { cleanUpAll(); } @After public void cleanUp() { cleanUpAll(); } @Test public void testInvokeSentinelWorks() { SentinelSofaRpcProviderFilter filter = new SentinelSofaRpcProviderFilter(); final String applicationName = "demo-provider"; final String interfaceResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService"; final String methodResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)"; SofaRequest request = mock(SofaRequest.class); when(request.getRequestProp("app")).thenReturn(applicationName); when(request.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC); when(request.getInterfaceName()).thenReturn(interfaceResourceName); when(request.getMethodName()).thenReturn("sayHello"); when(request.getMethodArgSigs()).thenReturn(new String[]{"java.lang.String", "int"}); when(request.getMethodArgs()).thenReturn(new Object[]{"Sentinel", 2020}); FilterInvoker filterInvoker = mock(FilterInvoker.class); when(filterInvoker.invoke(request)).thenAnswer(new Answer() { @Override public SofaResponse answer(InvocationOnMock invocationOnMock) throws Throwable { verifyInvocationStructure(applicationName, interfaceResourceName, methodResourceName); SofaResponse response = new SofaResponse(); response.setAppResponse("Hello Sentinel 2020"); return response; } }); // Before invoke assertNull(ContextUtil.getContext()); // Do invoke SofaResponse response = filter.invoke(filterInvoker, request); assertEquals("Hello Sentinel 2020", response.getAppResponse()); verify(filterInvoker).invoke(request); // After invoke, make sure exit context assertNull(ContextUtil.getContext()); } /** * Verify Sentinel invocation structure in memory: * EntranceNode(methodResourceName) * --InterfaceNode(interfaceResourceName) * ----MethodNode(methodResourceName) */ private void verifyInvocationStructure(String applicationName, String interfaceResourceName, String methodResourceName) { Context context = ContextUtil.getContext(); assertNotNull(context); assertEquals(methodResourceName, context.getName()); assertEquals(applicationName, context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); assertEquals(methodResourceName, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceResourceName, EntryType.IN); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); assertEquals(interfaceResourceName, interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(methodResourceName, EntryType.IN, 1, methodArguments); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); assertEquals(methodResourceName, methodResource.getName()); assertSame(EntryType.IN, methodResource.getEntryType()); // Verify curEntry Entry curEntry = context.getCurEntry(); assertSame(methodNode, curEntry.getCurNode()); assertSame(interfaceNode, curEntry.getLastNode()); // As context origin is not "", originNode should be created assertNotNull(curEntry.getOriginNode()); // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); // Different resource->Different ProcessorSlot->Different ClusterNode assertNotSame(methodClusterNode, interfaceClusterNode); // As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); assertEquals(1, methodOriginCountMap.size()); assertTrue(methodOriginCountMap.containsKey(applicationName)); Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(1, interfaceOriginCountMap.size()); assertTrue(interfaceOriginCountMap.containsKey(applicationName)); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/SofaRpcUtilsTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc; import com.alipay.sofa.rpc.core.request.SofaRequest; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link SofaRpcUtils}. * * @author cdfive */ public class SofaRpcUtilsTest { @Test public void testGetApplicationName() { SofaRequest request = new SofaRequest(); String applicationName = SofaRpcUtils.getApplicationName(request); assertEquals("", applicationName); request.addRequestProp("app", "test-app"); applicationName = SofaRpcUtils.getApplicationName(request); assertEquals("test-app", applicationName); } @Test public void testGetInterfaceResourceName() { SofaRequest request = new SofaRequest(); request.setInterfaceName("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService"); String interfaceResourceName = SofaRpcUtils.getInterfaceResourceName(request); assertEquals("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService", interfaceResourceName); } @Test public void testGetMethodResourceName() { SofaRequest request = new SofaRequest(); request.setInterfaceName("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService"); request.setMethodName("sayHello"); request.setMethodArgSigs(new String[]{"java.lang.String", "int"}); String methodResourceName = SofaRpcUtils.getMethodResourceName(request); assertEquals("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)", methodResourceName); } @Test public void testGetMethodArguments() { SofaRequest request = new SofaRequest(); request.setMethodArgs(new Object[]{"Sentinel", 2020}); Object[] arguments = SofaRpcUtils.getMethodArguments(request); assertEquals(arguments.length, 2); assertEquals("Sentinel", arguments[0]); assertEquals(2020, arguments[1]); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/fallback/DefaultSofaRpcFallbackTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Test cases for {@link DefaultSofaRpcFallback}. * * @author cdfive */ public class DefaultSofaRpcFallbackTest { @Test public void testHandle() { SofaRpcFallback sofaRpcFallback = new DefaultSofaRpcFallback(); BlockException blockException = mock(BlockException.class); boolean throwSentinelRpcException = false; boolean causeIsBlockException = false; try { sofaRpcFallback.handle(null, null, blockException); } catch (Exception e) { throwSentinelRpcException = e instanceof SentinelRpcException; causeIsBlockException = e.getCause() instanceof BlockException; } assertTrue(throwSentinelRpcException); assertTrue(causeIsBlockException); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/fallback/SofaRpcFallbackRegistryTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alipay.sofa.rpc.core.request.SofaRequest; import com.alipay.sofa.rpc.core.response.SofaResponse; import com.alipay.sofa.rpc.filter.FilterInvoker; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link SofaRpcFallbackRegistry}. * * @author cdfive */ public class SofaRpcFallbackRegistryTest { @Test public void testDefaultfallback() { // Test get default provider fallback SofaRpcFallback providerFallback = SofaRpcFallbackRegistry.getProviderFallback(); assertNotNull(providerFallback); assertTrue(providerFallback instanceof DefaultSofaRpcFallback); // Test get default consumer fallback SofaRpcFallback consumerFallback = SofaRpcFallbackRegistry.getConsumerFallback(); assertNotNull(consumerFallback); assertTrue(consumerFallback instanceof DefaultSofaRpcFallback); } @Test public void testCustomFallback() { // Test invoke custom provider fallback SofaRpcFallbackRegistry.setProviderFallback(new SofaRpcFallback() { @Override public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) { SofaResponse response = new SofaResponse(); response.setAppResponse("test provider response"); return response; } }); SofaResponse providerResponse = SofaRpcFallbackRegistry.getProviderFallback().handle(null, null, null); assertNotNull(providerResponse); assertEquals("test provider response", providerResponse.getAppResponse()); // Test invoke custom consumer fallback SofaRpcFallbackRegistry.setConsumerFallback(new SofaRpcFallback() { @Override public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) { SofaResponse response = new SofaResponse(); response.setAppResponse("test consumer response"); return response; } }); SofaResponse consumerResponse = SofaRpcFallbackRegistry.getConsumerFallback().handle(null, null, null); assertNotNull(consumerResponse); assertEquals("test consumer response", consumerResponse.getAppResponse()); // Reset to default provider and consumer fallback SofaRpcFallbackRegistry.setProviderFallback(new DefaultSofaRpcFallback()); SofaRpcFallbackRegistry.setConsumerFallback(new DefaultSofaRpcFallback()); } } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/service/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.service; /** * @author cdfive */ public interface DemoService { String sayHello(String name, int year); } ================================================ FILE: sentinel-adapter/sentinel-sofa-rpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/sofa/rpc/service/impl/DemoServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.sofa.rpc.service.impl; import com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService; /** * @author cdfive */ public class DemoServiceImpl implements DemoService { @Override public String sayHello(String name, int year) { return "Hello " + name + " " + year; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/README.md ================================================ # Sentinel Spring Cloud Gateway Adapter Sentinel provides integration module with Spring Cloud Gateway. The integration module is based on the Sentinel Reactor Adapter. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-spring-cloud-gateway-adapter x.y.z ``` Then you only need to inject the corresponding `SentinelGatewayFilter` and `SentinelGatewayBlockExceptionHandler` instance in Spring configuration. For example: ```java @Configuration public class GatewayConfiguration { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(-1) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } } ``` The gateway adapter will regard all `routeId` (defined in Spring properties) and all customized API definitions (defined in `GatewayApiDefinitionManager` of `sentinel-api-gateway-adapter-common` module) as resources. You can register various customized callback in `GatewayCallbackManager`: - `setBlockHandler`: register a customized `BlockRequestHandler` to handle the blocked request. The default implementation is `DefaultBlockRequestHandler`, which returns default message like `Blocked by Sentinel: FlowException`. ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-cloud-gateway-adapter jar 1.8 1.8 2.1.1.RELEASE 2.5.12 5.3.18 com.alibaba.csp sentinel-api-gateway-adapter-common com.alibaba.csp sentinel-reactor-adapter org.springframework.cloud spring-cloud-gateway-core ${spring.cloud.gateway.version} provided org.springframework spring-webflux ${spring.version} provided org.springframework.cloud spring-cloud-starter-gateway ${spring.cloud.gateway.version} test org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test junit junit test org.assertj assertj-core test org.mockito mockito-core test ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.GatewayApiMatcherManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig; import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * @author Eric Zhao * @since 1.6.0 */ public class SentinelGatewayFilter implements GatewayFilter, GlobalFilter, Ordered { private final int order; private final GatewayParamParser paramParser; public SentinelGatewayFilter() { this(Ordered.HIGHEST_PRECEDENCE); } public SentinelGatewayFilter(int order) { this(order, new ServerWebExchangeItemParser()); } public SentinelGatewayFilter(RequestItemParser serverWebExchangeItemParser) { this(Ordered.HIGHEST_PRECEDENCE, serverWebExchangeItemParser); } public SentinelGatewayFilter(int order, RequestItemParser requestItemParser) { AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); this.order = order; this.paramParser = new GatewayParamParser<>(requestItemParser); } @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); Mono asyncResult = chain.filter(exchange); if (route != null) { String routeId = route.getId(); Object[] params = paramParser.parseParameterFor(routeId, exchange, r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID); String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser()) .map(f -> f.apply(exchange)) .orElse(""); asyncResult = asyncResult.transform( new SentinelReactorTransformer<>(new EntryConfig(routeId, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, 1, params, new ContextConfig(contextName(routeId), origin))) ); } Set matchingApis = pickMatchingApiDefinitions(exchange); for (String apiName : matchingApis) { Object[] params = paramParser.parseParameterFor(apiName, exchange, r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME); asyncResult = asyncResult.transform( new SentinelReactorTransformer<>(new EntryConfig(apiName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, 1, params)) ); } return asyncResult; } private String contextName(String route) { return SentinelGatewayConstants.GATEWAY_CONTEXT_ROUTE_PREFIX + route; } Set pickMatchingApiDefinitions(ServerWebExchange exchange) { return GatewayApiMatcherManager.getApiMatcherMap().values() .stream() .filter(m -> m.test(exchange)) .map(WebExchangeApiMatcher::getApiName) .collect(Collectors.toSet()); } @Override public int getOrder() { return order; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.net.InetSocketAddress; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import org.springframework.http.HttpCookie; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class ServerWebExchangeItemParser implements RequestItemParser { @Override public String getPath(ServerWebExchange exchange) { return exchange.getRequest().getPath().value(); } @Override public String getRemoteAddress(ServerWebExchange exchange) { InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); if (remoteAddress == null) { return null; } return remoteAddress.getAddress().getHostAddress(); } @Override public String getHeader(ServerWebExchange exchange, String key) { return exchange.getRequest().getHeaders().getFirst(key); } @Override public String getUrlParam(ServerWebExchange exchange, String paramName) { return exchange.getRequest().getQueryParams().getFirst(paramName); } @Override public String getCookieValue(ServerWebExchange exchange, String cookieName) { return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName)) .map(HttpCookie::getValue) .orElse(null); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * @author Eric Zhao * @since 1.6.0 */ public final class GatewayApiMatcherManager { private static volatile Map API_MATCHER_MAP = new HashMap<>(); public static Map getApiMatcherMap() { return Collections.unmodifiableMap(API_MATCHER_MAP); } public static Optional getMatcher(final String apiName) { return Optional.ofNullable(apiName) .map(e -> API_MATCHER_MAP.get(apiName)); } public static Set getApiDefinitionSet() { return API_MATCHER_MAP.values() .stream() .map(WebExchangeApiMatcher::getApiDefinition) .collect(Collectors.toSet()); } static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) { Map apiMatcherMap = new HashMap<>(); for (ApiDefinition definition : definitions) { apiMatcherMap.put(definition.getApiName(), new WebExchangeApiMatcher(definition)); } API_MATCHER_MAP = apiMatcherMap; } private GatewayApiMatcherManager() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver; /** * @author Eric Zhao * @since 1.6.0 */ public class SpringCloudGatewayApiDefinitionChangeObserver implements ApiDefinitionChangeObserver { @Override public void onChange(Set apiDefinitions) { GatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.sc.route.RouteMatchers; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class WebExchangeApiMatcher extends AbstractApiMatcher { public WebExchangeApiMatcher(ApiDefinition apiDefinition) { super(apiDefinition); } @Override protected void initializeMatchers() { if (apiDefinition.getPredicateItems() != null) { apiDefinition.getPredicateItems().forEach(item -> fromApiPredicate(item).ifPresent(matchers::add)); } } private Optional> fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { if (item instanceof ApiPathPredicateItem) { return fromApiPathPredicate((ApiPathPredicateItem)item); } return Optional.empty(); } private Optional> fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) { String pattern = item.getPattern(); if (StringUtil.isBlank(pattern)) { return Optional.empty(); } switch (item.getMatchStrategy()) { case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX: return Optional.of(RouteMatchers.regexPath(pattern)); case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX: return Optional.of(RouteMatchers.antPath(pattern)); default: return Optional.of(RouteMatchers.exactPath(pattern)); } } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Reactive handler for the blocked request. * * @author Eric Zhao */ @FunctionalInterface public interface BlockRequestHandler { /** * Handle the blocked request. * * @param exchange server exchange object * @param t block exception * @return server response to return */ Mono handleRequest(ServerWebExchange exchange, Throwable t); } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import static org.springframework.web.reactive.function.BodyInserters.fromObject; /** * The default implementation of {@link BlockRequestHandler}. * Compatible with Spring WebFlux and Spring Cloud Gateway. * * @author Eric Zhao */ public class DefaultBlockRequestHandler implements BlockRequestHandler { private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: "; @Override public Mono handleRequest(ServerWebExchange exchange, Throwable ex) { if (acceptsHtml(exchange)) { return htmlErrorResponse(ex); } // JSON result by default. return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(fromObject(buildErrorResult(ex))); } private Mono htmlErrorResponse(Throwable ex) { return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.TEXT_PLAIN) .syncBody(DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } private ErrorResult buildErrorResult(Throwable ex) { return new ErrorResult(HttpStatus.TOO_MANY_REQUESTS.value(), DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } /** * Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot. */ private boolean acceptsHtml(ServerWebExchange exchange) { try { List acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept(); acceptedMediaTypes.remove(MediaType.ALL); MediaType.sortBySpecificityAndQuality(acceptedMediaTypes); return acceptedMediaTypes.stream() .anyMatch(MediaType.TEXT_HTML::isCompatibleWith); } catch (InvalidMediaTypeException ex) { return false; } } private static class ErrorResult { private final int code; private final String message; ErrorResult(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.util.function.Function; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public final class GatewayCallbackManager { private static final Function DEFAULT_ORIGIN_PARSER = (w) -> ""; /** * BlockRequestHandler: (serverExchange, exception) -> response */ private static volatile BlockRequestHandler blockHandler = new DefaultBlockRequestHandler(); /** * RequestOriginParser: (serverExchange) -> origin */ private static volatile Function requestOriginParser = DEFAULT_ORIGIN_PARSER; public static /*@NonNull*/ BlockRequestHandler getBlockHandler() { return blockHandler; } public static void resetBlockHandler() { GatewayCallbackManager.blockHandler = new DefaultBlockRequestHandler(); } public static void setBlockHandler(BlockRequestHandler blockHandler) { AssertUtil.notNull(blockHandler, "blockHandler cannot be null"); GatewayCallbackManager.blockHandler = blockHandler; } public static /*@NonNull*/ Function getRequestOriginParser() { return requestOriginParser; } public static void resetRequestOriginParser() { GatewayCallbackManager.requestOriginParser = DEFAULT_ORIGIN_PARSER; } public static void setRequestOriginParser(Function requestOriginParser) { AssertUtil.notNull(requestOriginParser, "requestOriginParser cannot be null"); GatewayCallbackManager.requestOriginParser = requestOriginParser; } private GatewayCallbackManager() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.net.URI; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.6.0 */ public class RedirectBlockRequestHandler implements BlockRequestHandler { private final URI uri; public RedirectBlockRequestHandler(String url) { AssertUtil.assertNotBlank(url, "url cannot be blank"); this.uri = URI.create(url); } @Override public Mono handleRequest(ServerWebExchange exchange, Throwable t) { return ServerResponse.temporaryRedirect(uri).build(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.exception; import java.util.List; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.function.Supplier; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.6.0 */ public class SentinelGatewayBlockExceptionHandler implements WebExceptionHandler { private List viewResolvers; private List> messageWriters; public SentinelGatewayBlockExceptionHandler(List viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers; this.messageWriters = serverCodecConfigurer.getWriters(); } private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) { return response.writeTo(exchange, contextSupplier.get()); } @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } // This exception handler only handles rejection by Sentinel. if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex) .flatMap(response -> writeResponse(response, exchange)); } private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } private final Supplier contextSupplier = () -> new ServerResponse.Context() { @Override public List> messageWriters() { return SentinelGatewayBlockExceptionHandler.this.messageWriters; } @Override public List viewResolvers() { return SentinelGatewayBlockExceptionHandler.this.viewResolvers; } }; } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class AntRoutePathMatcher implements Predicate { private final String pattern; private final PathMatcher pathMatcher; private final boolean canMatch; public AntRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.pathMatcher = new AntPathMatcher(); this.canMatch = pathMatcher.isPattern(pattern); } @Override public boolean test(ServerWebExchange exchange) { String path = exchange.getRequest().getPath().value(); if (canMatch) { return pathMatcher.match(pattern, path); } return false; } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import java.util.regex.Pattern; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class RegexRoutePathMatcher implements Predicate { private final String pattern; private final Pattern regex; public RegexRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.regex = Pattern.compile(pattern); } @Override public boolean test(ServerWebExchange exchange) { String path = exchange.getRequest().getPath().value(); return regex.matcher(path).matches(); } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public final class RouteMatchers { public static Predicate all() { return exchange -> true; } public static Predicate antPath(String pathPattern) { return new AntRoutePathMatcher(pathPattern); } public static Predicate exactPath(final String path) { return exchange -> exchange.getRequest().getPath().value().equals(path); } public static Predicate regexPath(String pathPattern) { return new RegexRoutePathMatcher(pathPattern); } private RouteMatchers() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver ================================================ com.alibaba.csp.sentinel.adapter.gateway.sc.api.SpringCloudGatewayApiDefinitionChangeObserver ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.util.Collections; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test cases for {@link SentinelGatewayFilter}. * * @author Eric Zhao */ public class SentinelGatewayFilterTest { @Test public void testPickMatchingApiDefinitions() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); ServerHttpRequest request = mock(ServerHttpRequest.class); when(exchange.getRequest()).thenReturn(request); RequestPath requestPath = mock(RequestPath.class); when(request.getPath()).thenReturn(requestPath); // Prepare API definitions. Set apiDefinitions = new HashSet<>(); String apiName1 = "some_customized_api"; ApiDefinition api1 = new ApiDefinition(apiName1) .setPredicateItems(Collections.singleton( new ApiPathPredicateItem().setPattern("/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX) )); String apiName2 = "another_customized_api"; ApiDefinition api2 = new ApiDefinition(apiName2) .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/something")); add(new ApiPathPredicateItem().setPattern("/other/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); apiDefinitions.add(api1); apiDefinitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions); SentinelGatewayFilter filter = new SentinelGatewayFilter(); when(requestPath.value()).thenReturn("/product/123"); Set matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName1)).isTrue(); when(requestPath.value()).thenReturn("/products"); assertThat(filter.pickMatchingApiDefinitions(exchange).size()).isZero(); when(requestPath.value()).thenReturn("/something"); matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName2)).isTrue(); when(requestPath.value()).thenReturn("/other/foo/3"); matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName2)).isTrue(); } @Before public void setUp() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } @After public void tearDown() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Eric Zhao */ public class SpringCloudGatewayParamParserTest { private final GatewayParamParser paramParser = new GatewayParamParser<>( new ServerWebExchangeItemParser() ); @Test public void testParseParametersNoParamItem() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); // Prepare gateway rules. Set rules = new HashSet<>(); String routeId1 = "my_test_route_A"; rules.add(new GatewayFlowRule(routeId1) .setCount(5) .setIntervalSec(1) ); GatewayRuleManager.loadRules(rules); Object[] params = paramParser.parseParameterFor(routeId1, exchange, e -> e.getResourceMode() == 0); assertThat(params.length).isEqualTo(1); } @Test public void testParseParametersWithItems() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); ServerHttpRequest request = mock(ServerHttpRequest.class); when(exchange.getRequest()).thenReturn(request); RequestPath requestPath = mock(RequestPath.class); when(request.getPath()).thenReturn(requestPath); // Prepare gateway rules. Set rules = new HashSet<>(); String routeId1 = "my_test_route_A"; String api1 = "my_test_route_B"; String headerName = "X-Sentinel-Flag"; String paramName = "p"; GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1) .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ); GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1) .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName(headerName) ); GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1) .setCount(20) .setIntervalSec(1) .setBurst(5) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1) .setCount(120) .setIntervalSec(10) .setBurst(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) ); GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); rules.add(routeRule1); rules.add(routeRule2); rules.add(routeRule3); rules.add(routeRule4); rules.add(apiRule1); GatewayRuleManager.loadRules(rules); String expectedHost = "hello.test.sentinel"; String expectedAddress = "66.77.88.99"; String expectedHeaderValue1 = "Sentinel"; String expectedUrlParamValue1 = "17"; mockClientHostAddress(request, expectedAddress); Map expectedHeaders = new HashMap() {{ put(headerName, expectedHeaderValue1); put("Host", expectedHost); }}; mockHeaders(request, expectedHeaders); mockSingleUrlParam(request, paramName, expectedUrlParamValue1); Object[] params = paramParser.parseParameterFor(routeId1, exchange, e -> e.getResourceMode() == 0); assertThat(params.length).isEqualTo(4); assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress); assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1); assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1); assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost); assertThat(paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 0).length).isZero(); String expectedUrlParamValue2 = "fs"; mockSingleUrlParam(request, paramName, expectedUrlParamValue2); params = paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 1); assertThat(params.length).isEqualTo(1); assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2); } private void mockClientHostAddress(/*@Mock*/ ServerHttpRequest request, String address) { InetSocketAddress socketAddress = mock(InetSocketAddress.class); when(request.getRemoteAddress()).thenReturn(socketAddress); InetAddress inetAddress = mock(InetAddress.class); when(inetAddress.getHostAddress()).thenReturn(address); when(socketAddress.getAddress()).thenReturn(inetAddress); } private void mockHeaders(/*@Mock*/ ServerHttpRequest request, Map headerMap) { HttpHeaders headers = mock(HttpHeaders.class); when(request.getHeaders()).thenReturn(headers); for (Map.Entry e : headerMap.entrySet()) { when(headers.getFirst(e.getKey())).thenReturn(e.getValue()); } } @SuppressWarnings("unchecked") private void mockUrlParams(/*@Mock*/ ServerHttpRequest request, Map paramMap) { MultiValueMap urlParams = mock(MultiValueMap.class); when(request.getQueryParams()).thenReturn(urlParams); for (Map.Entry e : paramMap.entrySet()) { when(urlParams.getFirst(e.getKey())).thenReturn(e.getValue()); } } @SuppressWarnings("unchecked") private void mockSingleUrlParam(/*@Mock*/ ServerHttpRequest request, String key, String value) { MultiValueMap urlParams = mock(MultiValueMap.class); when(request.getQueryParams()).thenReturn(urlParams); when(urlParams.getFirst(key)).thenReturn(value); } private void mockSingleHeader(/*@Mock*/ ServerHttpRequest request, String key, String value) { HttpHeaders headers = mock(HttpHeaders.class); when(request.getHeaders()).thenReturn(headers); when(headers.getFirst(key)).thenReturn(value); } @Before public void setUp() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } @After public void tearDown() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/README.md ================================================ # Sentinel Spring Cloud Gateway Adapter Sentinel provides integration module with Spring Cloud Gateway. The integration module is based on the Sentinel Reactor Adapter. > This module is quite similar to sentinel-spring-cloud-gateway-adapter and The difference is that this module has made some adaptations for webflux 6.x. > The usage is consistent with sentinel-spring-cloud-gateway-adapter, with the difference being that the maven dependency is different. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-spring-cloud-gateway-v6x-adapter x.y.z ``` Then you only need to inject the corresponding `SentinelGatewayFilter` and `SentinelGatewayBlockExceptionHandler` instance in Spring configuration. For example: ```java @Configuration public class GatewayConfiguration { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(-1) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } } ``` The gateway adapter will regard all `routeId` (defined in Spring properties) and all customized API definitions (defined in `GatewayApiDefinitionManager` of `sentinel-api-gateway-adapter-common` module) as resources. You can register various customized callback in `GatewayCallbackManager`: - `setBlockHandler`: register a customized `BlockRequestHandler` to handle the blocked request. The default implementation is `DefaultBlockRequestHandler`, which returns default message like `Blocked by Sentinel: FlowException`. ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-cloud-gateway-v6x-adapter jar 17 17 4.3.0 3.5.0 6.2.7 false com.alibaba.csp sentinel-api-gateway-adapter-common com.alibaba.csp sentinel-reactor-adapter org.springframework.cloud spring-cloud-gateway-server ${spring.cloud.gateway.version} provided org.springframework spring-webflux ${spring.version} provided org.springframework.cloud spring-cloud-starter-gateway ${spring.cloud.gateway.version} test org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test junit junit test org.assertj assertj-core test org.mockito mockito-core test org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} org.apache.maven.surefire surefire-junit47 3.2.5 ${skip.spring.v6x.test} ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.GatewayApiMatcherManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig; import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * @author Eric Zhao * @since 1.6.0 */ public class SentinelGatewayFilter implements GatewayFilter, GlobalFilter, Ordered { private final int order; private final GatewayParamParser paramParser; public SentinelGatewayFilter() { this(Ordered.HIGHEST_PRECEDENCE); } public SentinelGatewayFilter(int order) { this(order, new ServerWebExchangeItemParser()); } public SentinelGatewayFilter(RequestItemParser serverWebExchangeItemParser) { this(Ordered.HIGHEST_PRECEDENCE, serverWebExchangeItemParser); } public SentinelGatewayFilter(int order, RequestItemParser requestItemParser) { AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); this.order = order; this.paramParser = new GatewayParamParser<>(requestItemParser); } @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); Mono asyncResult = chain.filter(exchange); if (route != null) { String routeId = route.getId(); Object[] params = paramParser.parseParameterFor(routeId, exchange, r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID); String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser()) .map(f -> f.apply(exchange)) .orElse(""); asyncResult = asyncResult.transform( new SentinelReactorTransformer<>(new EntryConfig(routeId, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, 1, params, new ContextConfig(contextName(routeId), origin))) ); } Set matchingApis = pickMatchingApiDefinitions(exchange); for (String apiName : matchingApis) { Object[] params = paramParser.parseParameterFor(apiName, exchange, r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME); asyncResult = asyncResult.transform( new SentinelReactorTransformer<>(new EntryConfig(apiName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, 1, params)) ); } return asyncResult; } private String contextName(String route) { return SentinelGatewayConstants.GATEWAY_CONTEXT_ROUTE_PREFIX + route; } Set pickMatchingApiDefinitions(ServerWebExchange exchange) { return GatewayApiMatcherManager.getApiMatcherMap().values() .stream() .filter(m -> m.test(exchange)) .map(WebExchangeApiMatcher::getApiName) .collect(Collectors.toSet()); } @Override public int getOrder() { return order; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.net.InetSocketAddress; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import org.springframework.http.HttpCookie; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class ServerWebExchangeItemParser implements RequestItemParser { @Override public String getPath(ServerWebExchange exchange) { return exchange.getRequest().getPath().value(); } @Override public String getRemoteAddress(ServerWebExchange exchange) { InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); if (remoteAddress == null) { return null; } return remoteAddress.getAddress().getHostAddress(); } @Override public String getHeader(ServerWebExchange exchange, String key) { return exchange.getRequest().getHeaders().getFirst(key); } @Override public String getUrlParam(ServerWebExchange exchange, String paramName) { return exchange.getRequest().getQueryParams().getFirst(paramName); } @Override public String getCookieValue(ServerWebExchange exchange, String cookieName) { return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName)) .map(HttpCookie::getValue) .orElse(null); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/GatewayApiMatcherManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * @author Eric Zhao * @since 1.6.0 */ public final class GatewayApiMatcherManager { private static volatile Map API_MATCHER_MAP = new HashMap<>(); public static Map getApiMatcherMap() { return Collections.unmodifiableMap(API_MATCHER_MAP); } public static Optional getMatcher(final String apiName) { return Optional.ofNullable(apiName) .map(e -> API_MATCHER_MAP.get(apiName)); } public static Set getApiDefinitionSet() { return API_MATCHER_MAP.values() .stream() .map(WebExchangeApiMatcher::getApiDefinition) .collect(Collectors.toSet()); } static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) { Map apiMatcherMap = new HashMap<>(); for (ApiDefinition definition : definitions) { apiMatcherMap.put(definition.getApiName(), new WebExchangeApiMatcher(definition)); } API_MATCHER_MAP = apiMatcherMap; } private GatewayApiMatcherManager() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/SpringCloudGatewayApiDefinitionChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver; /** * @author Eric Zhao * @since 1.6.0 */ public class SpringCloudGatewayApiDefinitionChangeObserver implements ApiDefinitionChangeObserver { @Override public void onChange(Set apiDefinitions) { GatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/api/matcher/WebExchangeApiMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.sc.route.RouteMatchers; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class WebExchangeApiMatcher extends AbstractApiMatcher { public WebExchangeApiMatcher(ApiDefinition apiDefinition) { super(apiDefinition); } @Override protected void initializeMatchers() { if (apiDefinition.getPredicateItems() != null) { apiDefinition.getPredicateItems().forEach(item -> fromApiPredicate(item).ifPresent(matchers::add)); } } private Optional> fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { if (item instanceof ApiPathPredicateItem) { return fromApiPathPredicate((ApiPathPredicateItem)item); } return Optional.empty(); } private Optional> fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) { String pattern = item.getPattern(); if (StringUtil.isBlank(pattern)) { return Optional.empty(); } switch (item.getMatchStrategy()) { case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX: return Optional.of(RouteMatchers.regexPath(pattern)); case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX: return Optional.of(RouteMatchers.antPath(pattern)); default: return Optional.of(RouteMatchers.exactPath(pattern)); } } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/BlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Reactive handler for the blocked request. * * @author Eric Zhao */ @FunctionalInterface public interface BlockRequestHandler { /** * Handle the blocked request. * * @param exchange server exchange object * @param t block exception * @return server response to return */ Mono handleRequest(ServerWebExchange exchange, Throwable t); } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/DefaultBlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.util.MimeTypeUtils; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * The default implementation of {@link BlockRequestHandler}. * Compatible with Spring WebFlux v6x and Spring Cloud Gateway. * * @author uuuyuqi */ public class DefaultBlockRequestHandler implements BlockRequestHandler { private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: "; @Override public Mono handleRequest(ServerWebExchange exchange, Throwable ex) { if (acceptsHtml(exchange)) { return htmlErrorResponse(ex); } // JSON result by default. return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .bodyValue(buildErrorResult(ex)); } private Mono htmlErrorResponse(Throwable ex) { return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.TEXT_PLAIN) .bodyValue(DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } private ErrorResult buildErrorResult(Throwable ex) { return new ErrorResult(HttpStatus.TOO_MANY_REQUESTS.value(), DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } /** * Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot. */ private boolean acceptsHtml(ServerWebExchange exchange) { try { List acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept(); acceptedMediaTypes.remove(MediaType.ALL); MimeTypeUtils.sortBySpecificity(acceptedMediaTypes); return acceptedMediaTypes.stream() .anyMatch(MediaType.TEXT_HTML::isCompatibleWith); } catch (InvalidMediaTypeException ex) { return false; } } private static class ErrorResult { private final int code; private final String message; ErrorResult(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/GatewayCallbackManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.util.function.Function; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public final class GatewayCallbackManager { private static final Function DEFAULT_ORIGIN_PARSER = (w) -> ""; /** * BlockRequestHandler: (serverExchange, exception) -> response */ private static volatile BlockRequestHandler blockHandler = new DefaultBlockRequestHandler(); /** * RequestOriginParser: (serverExchange) -> origin */ private static volatile Function requestOriginParser = DEFAULT_ORIGIN_PARSER; public static /*@NonNull*/ BlockRequestHandler getBlockHandler() { return blockHandler; } public static void resetBlockHandler() { GatewayCallbackManager.blockHandler = new DefaultBlockRequestHandler(); } public static void setBlockHandler(BlockRequestHandler blockHandler) { AssertUtil.notNull(blockHandler, "blockHandler cannot be null"); GatewayCallbackManager.blockHandler = blockHandler; } public static /*@NonNull*/ Function getRequestOriginParser() { return requestOriginParser; } public static void resetRequestOriginParser() { GatewayCallbackManager.requestOriginParser = DEFAULT_ORIGIN_PARSER; } public static void setRequestOriginParser(Function requestOriginParser) { AssertUtil.notNull(requestOriginParser, "requestOriginParser cannot be null"); GatewayCallbackManager.requestOriginParser = requestOriginParser; } private GatewayCallbackManager() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/callback/RedirectBlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.callback; import java.net.URI; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.6.0 */ public class RedirectBlockRequestHandler implements BlockRequestHandler { private final URI uri; public RedirectBlockRequestHandler(String url) { AssertUtil.assertNotBlank(url, "url cannot be blank"); this.uri = URI.create(url); } @Override public Mono handleRequest(ServerWebExchange exchange, Throwable t) { return ServerResponse.temporaryRedirect(uri).build(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/exception/SentinelGatewayBlockExceptionHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.exception; import java.util.List; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.function.Supplier; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.6.0 */ public class SentinelGatewayBlockExceptionHandler implements WebExceptionHandler { private List viewResolvers; private List> messageWriters; public SentinelGatewayBlockExceptionHandler(List viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers; this.messageWriters = serverCodecConfigurer.getWriters(); } private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) { return response.writeTo(exchange, contextSupplier.get()); } @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } // This exception handler only handles rejection by Sentinel. if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex) .flatMap(response -> writeResponse(response, exchange)); } private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } private final Supplier contextSupplier = () -> new ServerResponse.Context() { @Override public List> messageWriters() { return SentinelGatewayBlockExceptionHandler.this.messageWriters; } @Override public List viewResolvers() { return SentinelGatewayBlockExceptionHandler.this.viewResolvers; } }; } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/AntRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class AntRoutePathMatcher implements Predicate { private final String pattern; private final PathMatcher pathMatcher; private final boolean canMatch; public AntRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.pathMatcher = new AntPathMatcher(); this.canMatch = pathMatcher.isPattern(pattern); } @Override public boolean test(ServerWebExchange exchange) { String path = exchange.getRequest().getPath().value(); if (canMatch) { return pathMatcher.match(pattern, path); } return false; } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RegexRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import java.util.regex.Pattern; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public class RegexRoutePathMatcher implements Predicate { private final String pattern; private final Pattern regex; public RegexRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.regex = Pattern.compile(pattern); } @Override public boolean test(ServerWebExchange exchange) { String path = exchange.getRequest().getPath().value(); return regex.matcher(path).matches(); } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/route/RouteMatchers.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc.route; import com.alibaba.csp.sentinel.util.function.Predicate; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.6.0 */ public final class RouteMatchers { public static Predicate all() { return exchange -> true; } public static Predicate antPath(String pathPattern) { return new AntRoutePathMatcher(pathPattern); } public static Predicate exactPath(final String path) { return exchange -> exchange.getRequest().getPath().value().equals(path); } public static Predicate regexPath(String pathPattern) { return new RegexRoutePathMatcher(pathPattern); } private RouteMatchers() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver ================================================ com.alibaba.csp.sentinel.adapter.gateway.sc.api.SpringCloudGatewayApiDefinitionChangeObserver ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SentinelGatewayFilterTest.java ================================================ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.util.Collections; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test cases for {@link SentinelGatewayFilter}. * * @author Eric Zhao */ public class SentinelGatewayFilterTest { @Test public void testPickMatchingApiDefinitions() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); ServerHttpRequest request = mock(ServerHttpRequest.class); when(exchange.getRequest()).thenReturn(request); RequestPath requestPath = mock(RequestPath.class); when(request.getPath()).thenReturn(requestPath); // Prepare API definitions. Set apiDefinitions = new HashSet<>(); String apiName1 = "some_customized_api"; ApiDefinition api1 = new ApiDefinition(apiName1) .setPredicateItems(Collections.singleton( new ApiPathPredicateItem().setPattern("/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX) )); String apiName2 = "another_customized_api"; ApiDefinition api2 = new ApiDefinition(apiName2) .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/something")); add(new ApiPathPredicateItem().setPattern("/other/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); apiDefinitions.add(api1); apiDefinitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions); SentinelGatewayFilter filter = new SentinelGatewayFilter(); when(requestPath.value()).thenReturn("/product/123"); Set matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName1)).isTrue(); when(requestPath.value()).thenReturn("/products"); assertThat(filter.pickMatchingApiDefinitions(exchange).size()).isZero(); when(requestPath.value()).thenReturn("/something"); matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName2)).isTrue(); when(requestPath.value()).thenReturn("/other/foo/3"); matchingApis = filter.pickMatchingApiDefinitions(exchange); assertThat(matchingApis.size()).isEqualTo(1); assertThat(matchingApis.contains(apiName2)).isTrue(); } @Before public void setUp() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } @After public void tearDown() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/sc/SpringCloudGatewayParamParserTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.sc; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Eric Zhao */ public class SpringCloudGatewayParamParserTest { private final GatewayParamParser paramParser = new GatewayParamParser<>( new ServerWebExchangeItemParser() ); @Test public void testParseParametersNoParamItem() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); // Prepare gateway rules. Set rules = new HashSet<>(); String routeId1 = "my_test_route_A"; rules.add(new GatewayFlowRule(routeId1) .setCount(5) .setIntervalSec(1) ); GatewayRuleManager.loadRules(rules); Object[] params = paramParser.parseParameterFor(routeId1, exchange, e -> e.getResourceMode() == 0); assertThat(params.length).isEqualTo(1); } @Test public void testParseParametersWithItems() { // Mock a request. ServerWebExchange exchange = mock(ServerWebExchange.class); ServerHttpRequest request = mock(ServerHttpRequest.class); when(exchange.getRequest()).thenReturn(request); RequestPath requestPath = mock(RequestPath.class); when(request.getPath()).thenReturn(requestPath); // Prepare gateway rules. Set rules = new HashSet<>(); String routeId1 = "my_test_route_A"; String api1 = "my_test_route_B"; String headerName = "X-Sentinel-Flag"; String paramName = "p"; GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1) .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ); GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1) .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName(headerName) ); GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1) .setCount(20) .setIntervalSec(1) .setBurst(5) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1) .setCount(120) .setIntervalSec(10) .setBurst(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) ); GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName(paramName) ); rules.add(routeRule1); rules.add(routeRule2); rules.add(routeRule3); rules.add(routeRule4); rules.add(apiRule1); GatewayRuleManager.loadRules(rules); String expectedHost = "hello.test.sentinel"; String expectedAddress = "66.77.88.99"; String expectedHeaderValue1 = "Sentinel"; String expectedUrlParamValue1 = "17"; mockClientHostAddress(request, expectedAddress); Map expectedHeaders = new HashMap() {{ put(headerName, expectedHeaderValue1); put("Host", expectedHost); }}; mockHeaders(request, expectedHeaders); mockSingleUrlParam(request, paramName, expectedUrlParamValue1); Object[] params = paramParser.parseParameterFor(routeId1, exchange, e -> e.getResourceMode() == 0); assertThat(params.length).isEqualTo(4); assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress); assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1); assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1); assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost); assertThat(paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 0).length).isZero(); String expectedUrlParamValue2 = "fs"; mockSingleUrlParam(request, paramName, expectedUrlParamValue2); params = paramParser.parseParameterFor(api1, exchange, e -> e.getResourceMode() == 1); assertThat(params.length).isEqualTo(1); assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2); } private void mockClientHostAddress(/*@Mock*/ ServerHttpRequest request, String address) { InetSocketAddress socketAddress = mock(InetSocketAddress.class); when(request.getRemoteAddress()).thenReturn(socketAddress); InetAddress inetAddress = mock(InetAddress.class); when(inetAddress.getHostAddress()).thenReturn(address); when(socketAddress.getAddress()).thenReturn(inetAddress); } private void mockHeaders(/*@Mock*/ ServerHttpRequest request, Map headerMap) { HttpHeaders headers = mock(HttpHeaders.class); when(request.getHeaders()).thenReturn(headers); for (Map.Entry e : headerMap.entrySet()) { when(headers.getFirst(e.getKey())).thenReturn(e.getValue()); } } @SuppressWarnings("unchecked") private void mockUrlParams(/*@Mock*/ ServerHttpRequest request, Map paramMap) { MultiValueMap urlParams = mock(MultiValueMap.class); when(request.getQueryParams()).thenReturn(urlParams); for (Map.Entry e : paramMap.entrySet()) { when(urlParams.getFirst(e.getKey())).thenReturn(e.getValue()); } } @SuppressWarnings("unchecked") private void mockSingleUrlParam(/*@Mock*/ ServerHttpRequest request, String key, String value) { MultiValueMap urlParams = mock(MultiValueMap.class); when(request.getQueryParams()).thenReturn(urlParams); when(urlParams.getFirst(key)).thenReturn(value); } private void mockSingleHeader(/*@Mock*/ ServerHttpRequest request, String key, String value) { HttpHeaders headers = mock(HttpHeaders.class); when(request.getHeaders()).thenReturn(headers); when(headers.getFirst(key)).thenReturn(value); } @Before public void setUp() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } @After public void tearDown() { GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>()); GatewayRuleManager.loadRules(new HashSet<>()); } } ================================================ FILE: sentinel-adapter/sentinel-spring-cloud-gateway-v6x-adapter/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/README.md ================================================ # Sentinel Spring RestClient Adapter ## Overview Sentinel Spring RestClient Adapter provides Sentinel integration for Spring Framework 6.0+ `RestClient`. With this adapter, you can easily add flow control, circuit breaking, and degradation features to HTTP requests made via `RestClient`. ## Features - Flow control (QPS limiting) - Circuit breaking (degradation) - Custom resource name extraction - Custom fallback responses - HTTP 5xx error tracing ## Requirements - Spring Framework 6.0+ - JDK 17+ - Sentinel Core 1.8.0+ ## Usage ### 1. Add Dependency ```xml com.alibaba.csp sentinel-spring-restclient-adapter ${sentinel.version} ``` ### 2. Basic Usage ```java import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientInterceptor; import org.springframework.web.client.RestClient; // Create RestClient with Sentinel interceptor RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); // Use RestClient to send requests (protected by Sentinel) String result = restClient.get() .uri("https://httpbin.org/get") .retrieve() .body(String.class); ``` ### 3. Custom Configuration ```java import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientConfig; import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientInterceptor; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; // Custom resource name extractor RestClientResourceExtractor customExtractor = request -> { // Example: normalize RESTful path parameters String path = request.getURI().getPath(); if (path.matches("/users/\\d+")) { path = "/users/{id}"; } return request.getMethod() + ":" + request.getURI().getHost() + path; }; // Custom fallback: throw a custom exception when blocked RestClientFallback customFallback = (request, body, execution, ex) -> { throw new RuntimeException("Service temporarily unavailable, please retry later", ex); }; // Create configuration SentinelRestClientConfig config = new SentinelRestClientConfig( "my-restclient:", // Resource name prefix customExtractor, customFallback ); // Create interceptor with custom configuration RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor(config)) .build(); ``` ### 4. Configure Sentinel Rules ```java import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import java.util.Collections; // Configure flow control rule FlowRule rule = new FlowRule("restclient:GET:https://httpbin.org/get"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(10); // Max 10 requests per second rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); ``` ## Core Components ### SentinelRestClientInterceptor The main interceptor implementation responsible for: - Creating Sentinel resources for each HTTP request - Catching BlockException and invoking fallback handler - Tracing exceptions and 5xx errors ### SentinelRestClientConfig Configuration class containing: - `resourcePrefix`: Resource name prefix (default: `restclient:`) - `resourceExtractor`: Resource name extractor - `fallback`: Fallback handler ### RestClientResourceExtractor Interface for resource name extraction, allowing customization of resource name generation logic. ### RestClientFallback Interface for fallback handling, invoked when requests are blocked by flow control or circuit breaking. ## Resource Name Format The default resource name format: `{prefix}{METHOD}:{URL}` Examples: - `restclient:GET:https://httpbin.org/get` - `restclient:POST:http://localhost:8080/api/users` ## Notes This adapter only supports `RestClient` from Spring Framework 6.0+, not `RestTemplate`. ## Integration with Spring Cloud Alibaba This adapter provides basic Sentinel integration. For Spring Cloud Alibaba projects: 1. Add auto-configuration support in `spring-cloud-starter-alibaba-sentinel` 2. Use `@SentinelRestClient` annotation for simplified configuration ## License Apache License 2.0 ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-restclient-adapter jar 6.1.0 3.2.0 6.1.0 false com.alibaba.csp sentinel-core org.springframework spring-web ${spring-web.version} provided junit junit test org.mockito mockito-core test com.alibaba fastjson test org.springframework.boot spring-boot-starter-web ${spring-boot.version} test org.springframework.boot spring-boot-test ${spring-boot.version} test org.springframework spring-test ${spring-test.version} test org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} ${skip.spring.v6x.test} ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.DefaultRestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.DefaultRestClientFallback; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Configuration for Sentinel RestClient interceptor. * * @author QHT, uuuyuqi */ public class SentinelRestClientConfig { public static final String DEFAULT_RESOURCE_PREFIX = "restclient:"; private final String resourcePrefix; private final RestClientResourceExtractor resourceExtractor; private final RestClientFallback fallback; public SentinelRestClientConfig() { this(DEFAULT_RESOURCE_PREFIX); } public SentinelRestClientConfig(String resourcePrefix) { this(resourcePrefix, new DefaultRestClientResourceExtractor(), new DefaultRestClientFallback()); } public SentinelRestClientConfig(RestClientResourceExtractor resourceExtractor, RestClientFallback fallback) { this(DEFAULT_RESOURCE_PREFIX, resourceExtractor, fallback); } public SentinelRestClientConfig(String resourcePrefix, RestClientResourceExtractor resourceExtractor, RestClientFallback fallback) { AssertUtil.notNull(resourceExtractor, "resourceExtractor cannot be null"); AssertUtil.notNull(fallback, "fallback cannot be null"); this.resourcePrefix = resourcePrefix; this.resourceExtractor = resourceExtractor; this.fallback = fallback; } public String getResourcePrefix() { return resourcePrefix; } public RestClientResourceExtractor getResourceExtractor() { return resourceExtractor; } public RestClientFallback getFallback() { return fallback; } @Override public String toString() { return "SentinelRestClientConfig{" + "resourcePrefix='" + resourcePrefix + '\'' + ", resourceExtractor=" + resourceExtractor + ", fallback=" + fallback + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientInterceptor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient; import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import java.io.IOException; import java.net.URI; /** * {@link ClientHttpRequestInterceptor} for integrating Sentinel with Spring's * {@link org.springframework.web.client.RestClient}. * *

This interceptor creates two levels of Sentinel resources for each request: *

    *
  • Host-level resource: {@code METHOD:scheme://host[:port]}, * e.g. {@code GET:https://httpbin.org}
  • *
  • Path-level resource: extracted by {@link RestClientResourceExtractor}, * by default: {@code METHOD:scheme://host[:port]/path}, * e.g. {@code GET:https://httpbin.org/get}
  • *
* *

This dual-level design allows: *

    *
  • Host-level flow control for overall traffic to a service
  • *
  • Path-level flow control for specific endpoints
  • *
  • Circuit breaking at either level
  • *
* *

Supports: *

    *
  • Flow control (QPS limiting)
  • *
  • Circuit breaking (degrade)
  • *
  • Custom resource name extraction via {@link RestClientResourceExtractor}
  • *
  • Custom fallback responses via {@link RestClientFallback}
  • *
* * @author QHT, uuuyuqi * @see SentinelRestClientConfig * @see RestClientResourceExtractor * @see RestClientFallback */ public class SentinelRestClientInterceptor implements ClientHttpRequestInterceptor { private final SentinelRestClientConfig config; public SentinelRestClientInterceptor() { this.config = new SentinelRestClientConfig(); } public SentinelRestClientInterceptor(SentinelRestClientConfig config) { AssertUtil.notNull(config, "config cannot be null"); this.config = config; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { URI uri = request.getURI(); String hostResource = buildHostResourceName(request, uri); String pathResource = buildPathResourceName(request); boolean entryWithPath = !hostResource.equals(pathResource); Entry hostEntry = null; Entry pathEntry = null; try { hostEntry = SphU.entry(hostResource, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); if (entryWithPath) { pathEntry = SphU.entry(pathResource, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); } ClientHttpResponse response = execution.execute(request, body); if (response.getStatusCode().is5xxServerError()) { RuntimeException ex = new RuntimeException("Server error: " + response.getStatusCode().value()); Tracer.traceEntry(ex, hostEntry); if (pathEntry != null) { Tracer.traceEntry(ex, pathEntry); } } return response; } catch (BlockException ex) { return handleBlockException(request, body, execution, ex); } catch (IOException ex) { // Path entry does not need to be traced if an IO exception occurred. Tracer.traceEntry(ex, hostEntry); throw ex; } finally { if (pathEntry != null) { pathEntry.exit(); } if (hostEntry != null) { hostEntry.exit(); } } } private String buildHostResourceName(HttpRequest request, URI uri) { String hostResource = request.getMethod().toString() + ":" + uri.getScheme() + "://" + uri.getHost() + (uri.getPort() == -1 ? "" : ":" + uri.getPort()); if (StringUtil.isNotBlank(config.getResourcePrefix())) { hostResource = config.getResourcePrefix() + hostResource; } return hostResource; } private String buildPathResourceName(HttpRequest request) { String pathResource = config.getResourceExtractor().extract(request); if (StringUtil.isNotBlank(config.getResourcePrefix())) { pathResource = config.getResourcePrefix() + pathResource; } return pathResource; } private ClientHttpResponse handleBlockException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { return config.getFallback().handle(request, body, execution, ex); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/DefaultRestClientResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient.extractor; import java.net.URI; import org.springframework.http.HttpRequest; /** * Default resource extractor for RestClient. * *

Extracts resource name in format: {@code METHOD:scheme://host[:port]/path} * *

Examples: *

    *
  • {@code GET:https://httpbin.org/get}
  • *
  • {@code POST:http://localhost:8080/api/users}
  • *
  • {@code GET:http://localhost:8080/api/users/123}
  • *
* *

Note: Query parameters are not included in the resource name by default. * Use a custom extractor if you need query parameters. * * @author QHT, uuuyuqi */ public class DefaultRestClientResourceExtractor implements RestClientResourceExtractor { @Override public String extract(HttpRequest request) { URI uri = request.getURI(); return request.getMethod().toString() + ":" + uri.getScheme() + "://" + uri.getHost() + (uri.getPort() == -1 ? "" : ":" + uri.getPort()) + uri.getPath(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/RestClientResourceExtractor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient.extractor; import org.springframework.http.HttpRequest; /** * Extractor for RestClient resource name. * * @author QHT, uuuyuqi */ public interface RestClientResourceExtractor { /** * Extracts the resource name from the HTTP request. * * @param request HTTP request entity * @return the resource name of current request */ String extract(HttpRequest request); } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/DefaultRestClientFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpResponse; /** * Default fallback handler for RestClient. * * @author QHT, uuuyuqi */ public class DefaultRestClientFallback implements RestClientFallback { @Override public ClientHttpResponse handle(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { // Just wrap and throw the exception. throw new SentinelRpcException(ex); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/RestClientFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpResponse; /** * Fallback handler for RestClient when request is blocked by Sentinel. * * @author QHT, uuuyuqi */ public interface RestClientFallback { /** * Handle the blocked request and return a fallback response. * * @param request HTTP request entity * @param body request body * @param execution request execution * @param ex the block exception * @return fallback response */ ClientHttpResponse handle(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex); } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/ManualTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.restclient; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.web.client.RestClient; import com.alibaba.csp.sentinel.slots.block.BlockException; import java.util.Collections; /** * Manual test for RestClient adapter. * * Run this class directly to test the adapter functionality. * * @author uuuyuqi */ public class ManualTest { public static void main(String[] args) { System.out.println("=== Sentinel RestClient Adapter Manual Test ===\n"); testBasicUsage(); testWithFlowControl(); testWithCustomExtractor(); System.out.println("\n=== All manual tests completed! ==="); } private static void testBasicUsage() { System.out.println("Test 1: Basic Usage"); System.out.println("--------------------"); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); try { String result = restClient.get() .uri("https://httpbin.org/get") .retrieve() .body(String.class); System.out.println("✅ Request successful!"); System.out.println("Response length: " + (result != null ? result.length() : 0) + " chars"); ClusterNode node = ClusterBuilderSlot.getClusterNode("restclient:GET:https://httpbin.org/get"); if (node != null) { System.out.println("✅ Sentinel statistics recorded!"); System.out.println(" Total requests: " + node.totalRequest()); System.out.println(" Success: " + node.totalSuccess()); } } catch (Exception e) { System.out.println("❌ Test failed: " + e.getMessage()); } System.out.println(); } private static void testWithFlowControl() { System.out.println("Test 2: Flow Control"); System.out.println("---------------------"); String resourceName = "restclient:GET:https://httpbin.org/delay/1"; FlowRule rule = new FlowRule(resourceName); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); try { restClient.get() .uri("https://httpbin.org/delay/1") .retrieve() .body(String.class); System.out.println("❌ Request should have been blocked!"); } catch (com.alibaba.csp.sentinel.slots.block.SentinelRpcException e) { System.out.println("✅ Request blocked as expected: " + e.getCause().getClass().getSimpleName()); } catch (Exception e) { System.out.println("❌ Unexpected exception: " + e.getMessage()); } FlowRuleManager.loadRules(Collections.emptyList()); System.out.println(); } private static void testWithCustomExtractor() { System.out.println("Test 3: Custom Resource Extractor"); System.out.println("-----------------------------------"); RestClientResourceExtractor customExtractor = request -> { String path = request.getURI().getPath(); if (path.matches("/status/\\d+")) { path = "/status/{code}"; } return request.getMethod().toString() + ":" + request.getURI().getHost() + path; }; RestClientFallback customFallback = (HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) -> { throw new RuntimeException("Custom fallback: " + ex.getClass().getSimpleName(), ex); }; SentinelRestClientConfig config = new SentinelRestClientConfig( "custom:", customExtractor, customFallback ); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor(config)) .build(); try { String result = restClient.get() .uri("https://httpbin.org/status/200") .retrieve() .body(String.class); System.out.println("Response: " + (result != null ? "OK" : "Empty")); System.out.println("✅ Custom extractor test completed!"); String expectedResource = "custom:GET:httpbin.org/status/{code}"; ClusterNode node = ClusterBuilderSlot.getClusterNode(expectedResource); if (node != null) { System.out.println("✅ Resource name normalized correctly!"); System.out.println(" Resource: " + expectedResource); } } catch (Exception e) { System.out.println("Response: " + e.getMessage()); } } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientConfigTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient; import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.DefaultRestClientFallback; import org.junit.Test; /** * Tests for {@link SentinelRestClientConfig}. * * @author uuuyuqi */ public class SentinelRestClientConfigTest { @Test(expected = IllegalArgumentException.class) public void testConfigSetExtractorNull() { new SentinelRestClientConfig(null, new DefaultRestClientFallback()); } @Test(expected = IllegalArgumentException.class) public void testConfigSetFallbackNull() { new SentinelRestClientConfig(new RestClientResourceExtractor() { @Override public String extract(org.springframework.http.HttpRequest request) { return request.getURI().toString(); } }, null); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientInterceptorSimpleTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.restclient; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.spring.restclient.app.TestApplication; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.RestClient; import java.util.Collections; import static org.junit.Assert.*; /** * Simple integration tests for {@link SentinelRestClientInterceptor}. * * @author uuuyuqi */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = { "server.port=8087" }) public class SentinelRestClientInterceptorSimpleTest { @Value("${server.port}") private Integer port; @Before public void setUp() { Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); FlowRuleManager.loadRules(Collections.emptyList()); DegradeRuleManager.loadRules(Collections.emptyList()); } @After public void tearDown() { Constants.ROOT.removeChildList(); ClusterBuilderSlot.getClusterNodeMap().clear(); FlowRuleManager.loadRules(Collections.emptyList()); DegradeRuleManager.loadRules(Collections.emptyList()); } @Test public void testBasicRequest() { String url = "http://localhost:" + port + "/test/hello"; RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); String result = restClient.get() .uri(url) .retrieve() .body(String.class); assertEquals("Hello, Sentinel!", result); System.out.println("Request completed successfully: " + result); } @Test public void testDualLevelResources() { String url = "http://localhost:" + port + "/test/hello"; String expectedHostResource = "restclient:GET:http://localhost:" + port; String expectedPathResource = "restclient:GET:http://localhost:" + port + "/test/hello"; RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); String result = restClient.get() .uri(url) .retrieve() .body(String.class); assertEquals("Hello, Sentinel!", result); System.out.println("✅ Request completed successfully"); try { ClusterNode hostNode = ClusterBuilderSlot.getClusterNode(expectedHostResource); if (hostNode != null) { System.out.println("✅ Host-level resource created: " + expectedHostResource); System.out.println(" Total requests: " + hostNode.totalRequest()); } ClusterNode pathNode = ClusterBuilderSlot.getClusterNode(expectedPathResource); if (pathNode != null) { System.out.println("✅ Path-level resource created: " + expectedPathResource); System.out.println(" Total requests: " + pathNode.totalRequest()); } } catch (Exception e) { System.out.println("Note: ClusterNode check skipped due to: " + e.getMessage()); } } @Test(expected = com.alibaba.csp.sentinel.slots.block.SentinelRpcException.class) public void testFlowControlBlocking() { String url = "http://localhost:" + port + "/test/hello"; String pathResource = "restclient:GET:" + url; FlowRule rule = new FlowRule(pathResource); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); restClient.get() .uri(url) .retrieve() .body(String.class); } @Test(expected = com.alibaba.csp.sentinel.slots.block.SentinelRpcException.class) public void testHostLevelFlowControl() throws InterruptedException { String url = "http://localhost:" + port + "/test/hello"; String rootResource = "restclient:GET:" + "http://localhost:" + port; FlowRule rule = new FlowRule(rootResource); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); restClient.get() .uri(url) .retrieve() .body(String.class); } @Test(expected = IllegalStateException.class) public void testCustomConfig() { String customPrefix = "my-api:"; String url = "http://localhost:" + port + "/test/hello"; FlowRule rule = new FlowRule("my-api:abc"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); SentinelRestClientConfig config = new SentinelRestClientConfig( customPrefix, request -> "abc", (a, b, c, d) -> { throw new IllegalStateException("custom fallback triggered"); }); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor(config)) .build(); restClient.get() .uri(url) .retrieve() .body(String.class); } @Test(expected = com.alibaba.csp.sentinel.slots.block.SentinelRpcException.class) public void testPostRequestFlowControl() { String url = "http://localhost:" + port + "/test/users"; String pathResource = "restclient:POST:" + url; FlowRule rule = new FlowRule(pathResource); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(rule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); restClient.post() .uri(url) .body("Test User") .retrieve() .body(String.class); } @Test public void testDegradeByExceptionRatio() { String url = "http://localhost:" + port + "/test/error"; String resourceName = "restclient:GET:" + url; DegradeRule degradeRule = new DegradeRule(resourceName); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); degradeRule.setCount(0.99); degradeRule.setMinRequestAmount(1); degradeRule.setStatIntervalMs(10 * 1000); degradeRule.setTimeWindow(30); degradeRule.setLimitApp("default"); DegradeRuleManager.loadRules(Collections.singletonList(degradeRule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); try { restClient.get() .uri(url) .retrieve() .body(String.class); } catch (Exception e) { System.out.println("First request failed with exception (expected): " + e.getClass().getSimpleName()); } try { String result = restClient.get() .uri(url) .retrieve() .body(String.class); assertNotNull("Should get fallback response after circuit opens", result); assertTrue("Response should indicate blocking", result.contains("blocked by Sentinel") || result.contains("DegradeException")); System.out.println("Degrade fallback response: " + result); } catch (Exception e) { System.out.println("Second request also threw exception: " + e.getMessage()); } } @Test public void testDegradeBySlowResponseTime() throws InterruptedException { String url = "http://localhost:" + port + "/test/delay"; String resourceName = "restclient:GET:" + url; DegradeRule degradeRule = new DegradeRule(resourceName); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT); degradeRule.setCount(50); degradeRule.setSlowRatioThreshold(0.1); degradeRule.setMinRequestAmount(1); degradeRule.setStatIntervalMs(10 * 1000); degradeRule.setTimeWindow(30); degradeRule.setLimitApp("default"); DegradeRuleManager.loadRules(Collections.singletonList(degradeRule)); RestClient restClient = RestClient.builder() .requestInterceptor(new SentinelRestClientInterceptor()) .build(); String result = restClient.get() .uri(url) .retrieve() .body(String.class); assertEquals("Delayed response", result); System.out.println("First slow request completed: " + result); Thread.sleep(100); try { result = restClient.get() .uri(url) .retrieve() .body(String.class); assertNotNull("Should get response", result); System.out.println("Second request response: " + result); } catch (Exception e) { System.out.println("Request failed: " + e.getMessage()); } } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/app/TestApplication.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.restclient.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Test application for RestClient adapter. * * @author uuuyuqi */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/app/TestController.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.restclient.app; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Test controller for RestClient adapter tests. * * @author uuuyuqi */ @RestController @RequestMapping("/test") public class TestController { @GetMapping("/hello") public String hello() { return "Hello, Sentinel!"; } @GetMapping("/users/{id}") public String getUser(@PathVariable("id") Long id) { return "User: " + id; } @PostMapping("/users") public String createUser(@RequestBody String user) { return "Created: " + user; } @GetMapping("/error") public ResponseEntity error() { return ResponseEntity.status(500).body("Server Error"); } @GetMapping("/delay") public String delay() throws InterruptedException { Thread.sleep(100); return "Delayed response"; } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/DefaultRestClientResourceExtractorTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.restclient.extractor; import org.junit.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import java.net.URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * Tests for {@link DefaultRestClientResourceExtractor}. * * @author uuuyuqi */ public class DefaultRestClientResourceExtractorTest { @Test public void testExtract() throws Exception { DefaultRestClientResourceExtractor extractor = new DefaultRestClientResourceExtractor(); HttpRequest request = new HttpRequest() { @Override public HttpMethod getMethod() { return HttpMethod.GET; } @Override public URI getURI() { return URI.create("https://httpbin.org/get"); } @Override public org.springframework.http.HttpHeaders getHeaders() { return new org.springframework.http.HttpHeaders(); } }; String resourceName = extractor.extract(request); assertNotNull(resourceName); assertEquals("GET:https://httpbin.org/get", resourceName); } @Test public void testExtractWithPort() throws Exception { DefaultRestClientResourceExtractor extractor = new DefaultRestClientResourceExtractor(); HttpRequest request = new HttpRequest() { @Override public HttpMethod getMethod() { return HttpMethod.POST; } @Override public URI getURI() { return URI.create("http://localhost:8080/api/users"); } @Override public org.springframework.http.HttpHeaders getHeaders() { return new org.springframework.http.HttpHeaders(); } }; String resourceName = extractor.extract(request); assertNotNull(resourceName); assertEquals("POST:http://localhost:8080/api/users", resourceName); } } ================================================ FILE: sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/DefaultRestClientFallbackTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.restclient.fallback; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import java.net.URI; import static org.junit.Assert.assertSame; /** * Tests for {@link DefaultRestClientFallback}. * * @author uuuyuqi */ public class DefaultRestClientFallbackTest { @Test(expected = SentinelRpcException.class) public void testHandleThrowsException() { DefaultRestClientFallback fallback = new DefaultRestClientFallback(); HttpRequest request = new HttpRequest() { @Override public HttpMethod getMethod() { return HttpMethod.GET; } @Override public URI getURI() { return URI.create("https://httpbin.org/get"); } @Override public org.springframework.http.HttpHeaders getHeaders() { return new org.springframework.http.HttpHeaders(); } }; FlowException ex = new FlowException("test", "default"); fallback.handle(request, new byte[0], null, ex); } @Test public void testHandleWrapsBlockException() { DefaultRestClientFallback fallback = new DefaultRestClientFallback(); HttpRequest request = new HttpRequest() { @Override public HttpMethod getMethod() { return HttpMethod.GET; } @Override public URI getURI() { return URI.create("https://httpbin.org/get"); } @Override public org.springframework.http.HttpHeaders getHeaders() { return new org.springframework.http.HttpHeaders(); } }; FlowException ex = new FlowException("test", "default"); try { fallback.handle(request, new byte[0], null, ex); } catch (SentinelRpcException e) { assertSame(ex, e.getCause()); } } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/README.md ================================================ # Sentinel Spring WebFlux Adapter Sentinel provides integration module with Spring WebFlux, so reactive web applications can also leverage Sentinel's flow control and circuit breaking to achieve reliability. The integration module is based on the Sentinel Reactor Adapter. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-spring-webflux-adapter x.y.z ``` Then you only need to inject the corresponding `SentinelWebFluxFilter` and `SentinelBlockExceptionHandler` instance in Spring configuration. For example: ```java @Configuration public class WebFluxConfig { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public WebFluxConfig(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(-1) public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { // Register the block exception handler for Spring WebFlux. return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public SentinelWebFluxFilter sentinelWebFluxFilter() { // Register the Sentinel WebFlux filter. return new SentinelWebFluxFilter(); } } ``` You can register various customized callback in `WebFluxCallbackManager`: - `setBlockHandler`: register a customized `BlockRequestHandler` to handle the blocked request. The default implementation is `DefaultBlockRequestHandler`, which returns default message like `Blocked by Sentinel: FlowException`. - `setUrlCleaner`: used for normalization of URL. The function type is `(ServerWebExchange, String) → String`, which means `(webExchange, originalUrl) → finalUrl`, if the finalUrl is `"""` or `null`, the URLs will be excluded (since Sentinel 1.7.0).. - `setRequestOriginParser`: used to resolve the origin from the HTTP request. The function type is `ServerWebExchange → String`. You can also refer to the demo: [sentinel-demo-spring-webflux](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-spring-webflux). ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-webflux-adapter 1.8 1.8 5.3.18 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-reactor-adapter org.springframework spring-webflux ${spring.version} provided junit junit test org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxFilter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux; import java.util.Optional; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig; import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.5.0 */ public class SentinelWebFluxFilter implements WebFilter { public static final String SENTINEL_SPRING_WEBFLUX_CONTEXT_NAME = "sentinel_spring_webflux_context"; @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { // Maybe we can get the URL pattern elsewhere via: // exchange.getAttributeOrDefault(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, path) String path = exchange.getRequest().getPath().value(); String finalPath = WebFluxCallbackManager.getUrlCleaner().apply(exchange, path); if (StringUtil.isEmpty(finalPath)) { return chain.filter(exchange); } return chain.filter(exchange) .transform(buildSentinelTransformer(exchange, finalPath)); } private SentinelReactorTransformer buildSentinelTransformer(ServerWebExchange exchange, String finalPath) { String origin = Optional.ofNullable(WebFluxCallbackManager.getRequestOriginParser()) .map(f -> f.apply(exchange)) .orElse(EMPTY_ORIGIN); return new SentinelReactorTransformer<>(new EntryConfig(finalPath, ResourceTypeConstants.COMMON_WEB, EntryType.IN, new ContextConfig(getContextName(exchange), origin))); } protected String getContextName(ServerWebExchange exchange){ return SENTINEL_SPRING_WEBFLUX_CONTEXT_NAME; } private static final String EMPTY_ORIGIN = ""; } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webflux/callback/BlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.callback; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * Reactive handler for the blocked request. * * @author Eric Zhao * @since 1.5.0 */ @FunctionalInterface public interface BlockRequestHandler { /** * Handle the blocked request. * * @param exchange server exchange object * @param t block exception * @return server response to return */ Mono handleRequest(ServerWebExchange exchange, Throwable t); } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webflux/callback/DefaultBlockRequestHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.callback; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import static org.springframework.web.reactive.function.BodyInserters.fromObject; /** * The default implementation of {@link BlockRequestHandler}. * * @author Eric Zhao * @since 1.5.0 */ public class DefaultBlockRequestHandler implements BlockRequestHandler { private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: "; @Override public Mono handleRequest(ServerWebExchange exchange, Throwable ex) { if (acceptsHtml(exchange)) { return htmlErrorResponse(ex); } // JSON result by default. return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(fromObject(buildErrorResult(ex))); } private Mono htmlErrorResponse(Throwable ex) { return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.TEXT_PLAIN) .syncBody(DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } private ErrorResult buildErrorResult(Throwable ex) { return new ErrorResult(HttpStatus.TOO_MANY_REQUESTS.value(), DEFAULT_BLOCK_MSG_PREFIX + ex.getClass().getSimpleName()); } /** * Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot. */ private boolean acceptsHtml(ServerWebExchange exchange) { try { List acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept(); acceptedMediaTypes.remove(MediaType.ALL); MediaType.sortBySpecificityAndQuality(acceptedMediaTypes); return acceptedMediaTypes.stream() .anyMatch(MediaType.TEXT_HTML::isCompatibleWith); } catch (InvalidMediaTypeException ex) { return false; } } private static class ErrorResult { private final int code; private final String message; ErrorResult(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webflux/callback/WebFluxCallbackManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.callback; import java.util.function.BiFunction; import java.util.function.Function; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.web.server.ServerWebExchange; /** * @author Eric Zhao * @since 1.5.0 */ public final class WebFluxCallbackManager { private static final BiFunction DEFAULT_URL_CLEANER = (w, url) -> url; private static final Function DEFAULT_ORIGIN_PARSER = (w) -> ""; /** * BlockRequestHandler: (serverExchange, exception) -> response */ private static volatile BlockRequestHandler blockHandler = new DefaultBlockRequestHandler(); /** * UrlCleaner: (serverExchange, originalUrl) -> finalUrl */ private static volatile BiFunction urlCleaner = DEFAULT_URL_CLEANER; /** * RequestOriginParser: (serverExchange) -> origin */ private static volatile Function requestOriginParser = DEFAULT_ORIGIN_PARSER; public static /*@NonNull*/ BlockRequestHandler getBlockHandler() { return blockHandler; } public static void resetBlockHandler() { WebFluxCallbackManager.blockHandler = new DefaultBlockRequestHandler(); } public static void setBlockHandler(BlockRequestHandler blockHandler) { AssertUtil.notNull(blockHandler, "blockHandler cannot be null"); WebFluxCallbackManager.blockHandler = blockHandler; } public static /*@NonNull*/ BiFunction getUrlCleaner() { return urlCleaner; } public static void resetUrlCleaner() { WebFluxCallbackManager.urlCleaner = DEFAULT_URL_CLEANER; } public static void setUrlCleaner(BiFunction urlCleaner) { AssertUtil.notNull(urlCleaner, "urlCleaner cannot be null"); WebFluxCallbackManager.urlCleaner = urlCleaner; } public static /*@NonNull*/ Function getRequestOriginParser() { return requestOriginParser; } public static void resetRequestOriginParser() { WebFluxCallbackManager.requestOriginParser = DEFAULT_ORIGIN_PARSER; } public static void setRequestOriginParser(Function requestOriginParser) { AssertUtil.notNull(requestOriginParser, "requestOriginParser cannot be null"); WebFluxCallbackManager.requestOriginParser = requestOriginParser; } private WebFluxCallbackManager() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webflux/exception/SentinelBlockExceptionHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.exception; import java.util.List; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.function.Supplier; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; /** * @author Eric Zhao * @since 1.5.0 */ public class SentinelBlockExceptionHandler implements WebExceptionHandler { private List viewResolvers; private List> messageWriters; public SentinelBlockExceptionHandler(List viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers; this.messageWriters = serverCodecConfigurer.getWriters(); } private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) { return response.writeTo(exchange, contextSupplier.get()); } @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } // This exception handler only handles rejection by Sentinel. if (!BlockException.isBlockException(ex)) { return Mono.error(ex); } return handleBlockedRequest(exchange, ex) .flatMap(response -> writeResponse(response, exchange)); } private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return WebFluxCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } private final Supplier contextSupplier = () -> new ServerResponse.Context() { @Override public List> messageWriters() { return SentinelBlockExceptionHandler.this.messageWriters; } @Override public List viewResolvers() { return SentinelBlockExceptionHandler.this.viewResolvers; } }; } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.webflux; import java.util.ArrayList; import java.util.Collections; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager; import com.alibaba.csp.sentinel.adapter.spring.webflux.test.WebFluxTestApplication; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import org.hamcrest.core.StringContains; import org.junit.After; 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.context.SpringBootTest.WebEnvironment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.ServerResponse; import static org.junit.Assert.*; /** * @author Eric Zhao */ @RunWith(SpringRunner.class) @SpringBootTest(classes = WebFluxTestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) public class SentinelWebFluxIntegrationTest { private static final String HELLO_STR = "Hello!"; private static final String BLOCK_MSG_PREFIX = "Blocked by Sentinel: "; @Autowired private WebTestClient webClient; private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testWebFluxFilterBasic() throws Exception { String url = "/hello"; this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo(HELLO_STR); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testWebFluxRouterFunction() throws Exception { String url = "/router/hello"; this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo(HELLO_STR); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); configureRulesFor(url, 0); this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .exchange() .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) .expectBody(String.class).value(StringContains.containsString(BLOCK_MSG_PREFIX)); } @Test public void testCustomizedUrlCleaner() throws Exception { final String fooPrefix = "/foo/"; String url1 = fooPrefix + 1; String url2 = fooPrefix + 2; WebFluxCallbackManager.setUrlCleaner(((exchange, originUrl) -> { if (originUrl.startsWith(fooPrefix)) { return "/foo/*"; } return originUrl; })); this.webClient.get() .uri(url1) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello 1"); this.webClient.get() .uri(url2) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello 2"); ClusterNode cn = ClusterBuilderSlot.getClusterNode(fooPrefix + "*"); assertEquals(2, cn.passQps(), 0.01); assertNull(ClusterBuilderSlot.getClusterNode(url1)); assertNull(ClusterBuilderSlot.getClusterNode(url2)); WebFluxCallbackManager.resetUrlCleaner(); } @Test public void testCustomizedIgnoreUrlCleaner() throws Exception { final String fooPrefix = "/foo/"; String url1 = fooPrefix + 1; WebFluxCallbackManager.setUrlCleaner(((exchange, originUrl) -> { if (originUrl.startsWith(fooPrefix)) { return ""; } return originUrl; })); this.webClient.get() .uri(url1) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello 1"); assertNull(ClusterBuilderSlot.getClusterNode(url1)); WebFluxCallbackManager.resetUrlCleaner(); } @Test public void testCustomizedBlockRequestHandler() throws Exception { String url = "/error"; String prefix = "blocked: "; WebFluxCallbackManager.setBlockHandler((exchange, t) -> ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .syncBody(prefix + t.getMessage())); this.webClient.get() .uri(url) .exchange() .expectStatus().isOk() .expectBody(String.class).value(StringContains.containsString(prefix)); WebFluxCallbackManager.resetBlockHandler(); } @Test public void testCustomizedRequestOriginParser() throws Exception { String url = "/hello"; String limitOrigin = "userA"; final String headerName = "S-User"; configureRulesFor(url, 0, limitOrigin); WebFluxCallbackManager.setRequestOriginParser(exchange -> { String origin = exchange.getRequest().getHeaders().getFirst(headerName); return origin != null ? origin : ""; }); this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .header(headerName, "userB") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo(HELLO_STR); // This will be blocked. this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .header(headerName, limitOrigin) .exchange() .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) .expectBody(String.class).value(StringContains.containsString(BLOCK_MSG_PREFIX)); this.webClient.get() .uri(url) .accept(MediaType.TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo(HELLO_STR); WebFluxCallbackManager.resetRequestOriginParser(); } @Before public void setUp() { FlowRuleManager.loadRules(new ArrayList<>()); ClusterBuilderSlot.resetClusterNodes(); } @After public void cleanUp() { FlowRuleManager.loadRules(new ArrayList<>()); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Eric Zhao */ @SpringBootApplication public class WebFluxTestApplication { public static void main(String[] args) { SpringApplication.run(WebFluxTestApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.test; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter; import com.alibaba.csp.sentinel.adapter.spring.webflux.exception.SentinelBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** * @author Eric Zhao */ @Configuration public class WebFluxTestConfig { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public WebFluxTestConfig(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(-1) public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { // Register the block exception handler for Spring WebFlux. return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public SentinelWebFluxFilter sentinelWebFluxFilter() { // Register the Sentinel WebFlux filter. return new SentinelWebFluxFilter(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestController.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.test; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author Eric Zhao */ @RestController public class WebFluxTestController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } @GetMapping("/flux") public Flux apiFlux() { return Flux.range(0, 5); } @GetMapping("/error") public Mono apiError() { return Mono.error(new FlowException("testWebFluxError")); } @GetMapping("/foo/{id}") public String apiFoo(@PathVariable("id") Long id) { return "Hello " + id; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestRouter.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webflux.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import static org.springframework.web.reactive.function.BodyInserters.fromObject; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RouterFunctions.route; /** * @author liqiangz */ @Configuration public class WebFluxTestRouter { @Bean RouterFunction routingFunction() { return route(GET("/router/hello"), req -> ServerResponse.ok().body(fromObject("Hello!"))); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/README.md ================================================ # Sentinel Spring MVC Adapter ## Introduction Sentinel provides integration for Spring Web to enable flow control for web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-spring-webmvc-adapter x.y.z ``` Then we could add a configuration bean to configure the interceptor: ```java @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); // Enable the HTTP method prefix. config.setHttpMethodSpecify(true); // Add to the interceptor list. registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } @Bean public SentinelExceptionAware sentinelExceptionAware(){ //Make exception visible to Sentinel if you have configured ExceptionHandler return new SentinelExceptionAware(); } } ``` Then Sentinel will extract URL patterns defined in Web Controller as the web resource (e.g. `/foo/{id}`). ## Configuration ### Block handling Sentinel Spring Web adapter provides a `BlockExceptionHandler` interface to handle the blocked requests. We could set the handler via `SentinelWebMvcTotalConfig#setBlockExceptionHandler()` method. By default the interceptor will throw out the `BlockException`. We need to set a global exception handler function in Spring to handle it. An example: ```java @ControllerAdvice @Order(0) public class SentinelBlockExceptionHandlerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(BlockException.class) @ResponseBody public String sentinelBlockHandler(BlockException e) { AbstractRule rule = e.getRule(); logger.info("Blocked by Sentinel: {}", rule.toString()); return "Blocked by Sentinel"; } } ``` We've provided a `DefaultBlockExceptionHandler`. When a request is blocked, the handler will return a default page indicating the request is rejected (`Blocked by Sentinel (flow limiting)`). The HTTP status code of the default block page is **429 (Too Many Requests)**. We could also implement our implementation of the `BlockExceptionHandler` interface and set to the config object. An example: ```java SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setBlockExceptionHandler((request, response, e) -> { String resourceName = e.getRule().getResource(); // Depending on your situation, you can choose to process or throw if ("/hello".equals(resourceName)) { // Do something ...... response.getWriter().write("Blocked by Sentinel"); } else { // Handle it in global exception handling throw e; } }); ``` ### Customized configuration - Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig`: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | `blockExceptionHandler` | The handler that handles the block request | `BlockExceptionHandler` | null (throw out the BlockException) | | `originParser` | Extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request | `RequestOriginParser` | - | - `SentinelWebMvcConfig` configuration: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. | `UrlCleaner` | - | | requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_entry_attr` | | httpMethodSpecify | Specify whether the URL resource name should contain the HTTP method prefix (e.g. `POST:`). | `boolean` | `false` | | webContextUnify | Specify whether unify web context(i.e. use the default context name). | `boolean` | `true` | - `SentinelWebMvcTotalConfig` configuration: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | totalResourceName | The resource name in `SentinelTotalInterceptor` | `String` | `spring-mvc-total-url-request` | | requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_total_entry_attr` | ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-webmvc-adapter jar 5.3.18 2.5.12 3.1.0 com.alibaba.csp sentinel-core javax.servlet javax.servlet-api ${servlet.api.version} provided org.springframework spring-webmvc ${spring.version} provided org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test com.alibaba fastjson test junit junit test ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.BaseWebMvcConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Objects; /** * Since request may be reprocessed in flow if any forwarding or including or other action * happened (see {@link javax.servlet.ServletRequest#getDispatcherType()}) we will only * deal with the initial request. So we use reference count to track in * dispathing "onion" though which we could figure out whether we are in initial type "REQUEST". * That means the sub-requests which we rarely meet in practice will NOT be recorded in Sentinel. *

* How to implement a forward sub-request in your action: *

 * initalRequest() {
 *     ModelAndView mav = new ModelAndView();
 *     mav.setViewName("another");
 *     return mav;
 * }
 * 
* * @author kaizi2009 * @since 1.7.1 */ public abstract class AbstractSentinelInterceptor implements AsyncHandlerInterceptor { public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context"; private static final String EMPTY_ORIGIN = ""; private final BaseWebMvcConfig baseWebMvcConfig; public AbstractSentinelInterceptor(BaseWebMvcConfig config) { AssertUtil.notNull(config, "BaseWebMvcConfig should not be null"); AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank"); this.baseWebMvcConfig = config; } /** * @param request * @param rcKey * @param step * @return reference count after increasing (initial value as zero to be increased) */ private Integer increaseReference(HttpServletRequest request, String rcKey, int step) { Object obj = request.getAttribute(rcKey); if (obj == null) { // initial obj = 0; } Integer newRc = (Integer) obj + step; request.setAttribute(rcKey, newRc); return newRc; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String resourceName = getResourceName(request); if (StringUtil.isEmpty(resourceName)) { return true; } if (increaseReference(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) { return true; } // Parse the request origin using registered origin parser. String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin); Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry); return true; } catch (BlockException e) { try { handleBlockException(request, response, e); } finally { ContextUtil.exit(); } return false; } } /** * Return the resource name of the target web resource. * * @param request web request * @return the resource name of the target web resource. */ protected abstract String getResourceName(HttpServletRequest request); /** * Return the context name of the target web resource. * * @param request web request * @return the context name of the target web resource. */ protected String getContextName(HttpServletRequest request) { return SENTINEL_SPRING_WEB_CONTEXT_NAME; } /** * When a handler starts an asynchronous request, the DispatcherServlet exits without invoking postHandle and afterCompletion * Called instead of postHandle and afterCompletion to exit the context and clean thread-local variables when the handler is being executed concurrently. * * @param request the current request * @param response the current response * @param handler the handler (or {@link HandlerMethod}) that started async * execution, for type and/or instance examination */ @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { exit(request); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { exit(request, ex); } private void exit(HttpServletRequest request) { exit(request, null); } private void exit(HttpServletRequest request, Exception ex) { if (increaseReference(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) { return; } Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName()); if (entry == null) { // should not happen RecordLog.warn("[{}] No entry found in request, key: {}", getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName()); return; } traceExceptionAndExit(entry, ex); removeEntryInRequest(request); ContextUtil.exit(); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) { Object entryObject = request.getAttribute(attrKey); return entryObject == null ? null : (Entry) entryObject; } protected void removeEntryInRequest(HttpServletRequest request) { request.removeAttribute(baseWebMvcConfig.getRequestAttributeName()); } protected void traceExceptionAndExit(Entry entry, Exception ex) { if (entry == null) { return; } HttpServletRequest request = getHttpServletRequest(); if (request != null && ex == null && increaseReference(request, this.baseWebMvcConfig.getRequestRefName() + ":" + BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME, 1) == 1) { //Each interceptor can only catch exception once ex = (Exception) request.getAttribute(BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME); } if (ex != null) { Tracer.traceEntry(ex, entry); } entry.exit(); } protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { // Throw BlockException directly. Users need to handle it in Spring global exception handler. throw e; } } protected String parseOrigin(HttpServletRequest request) { String origin = EMPTY_ORIGIN; if (baseWebMvcConfig.getOriginParser() != null) { origin = baseWebMvcConfig.getOriginParser().parseOrigin(request); if (StringUtil.isEmpty(origin)) { return EMPTY_ORIGIN; } } return origin; } private HttpServletRequest getHttpServletRequest() { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return Objects.isNull(servletRequestAttributes) ? null : servletRequestAttributes.getRequest(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelExceptionAware.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.BaseWebMvcConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.core.annotation.Order; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Make exception visible to Sentinel.SentinelExceptionAware should be front of ExceptionHandlerExceptionResolver * whose order is 0 {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver} * * @author lemonJ */ @Order(-1) public class SentinelExceptionAware implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { addExceptionToRequest(request, ex); return null; } private void addExceptionToRequest(HttpServletRequest httpServletRequest, Exception exception) { if(BlockException.isBlockException(exception)){ return; } httpServletRequest.setAttribute(BaseWebMvcConfig.REQUEST_REF_EXCEPTION_NAME, exception); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner; import javax.servlet.http.HttpServletRequest; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.web.servlet.HandlerMapping; /** * Spring Web MVC interceptor that integrates with Sentinel. *

* This will record resource as `${uri}`. * * @author kaizi2009 * @since 1.7.1 */ public class SentinelWebInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcConfig config; public SentinelWebInterceptor() { this(new SentinelWebMvcConfig()); } public SentinelWebInterceptor(SentinelWebMvcConfig config) { super(config); if (config == null) { // Use the default config by default. this.config = new SentinelWebMvcConfig(); } else { this.config = config; } } @Override protected String getResourceName(HttpServletRequest request) { // Resolve the Spring Web URL pattern from the request attribute. Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (resourceNameObject == null || !(resourceNameObject instanceof String)) { return null; } String resourceName = (String) resourceNameObject; UrlCleaner urlCleaner = config.getUrlCleaner(); if (urlCleaner != null) { resourceName = urlCleaner.clean(resourceName); } // Add method specification if necessary if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) { resourceName = request.getMethod().toUpperCase() + ":" + resourceName; } return resourceName; } @Override protected String getContextName(HttpServletRequest request) { if (config.isWebContextUnify()) { return super.getContextName(request); } return getResourceName(request); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebTotalInterceptor.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig; import javax.servlet.http.HttpServletRequest; /** * The web interceptor for all requests, which will unify all URL as * a single resource name (configured in {@link SentinelWebMvcTotalConfig}). * * @author kaizi2009 * @since 1.7.1 */ public class SentinelWebTotalInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcTotalConfig config; public SentinelWebTotalInterceptor(SentinelWebMvcTotalConfig config) { super(config); if (config == null) { this.config = new SentinelWebMvcTotalConfig(); } else { this.config = config; } } public SentinelWebTotalInterceptor() { this(new SentinelWebMvcTotalConfig()); } @Override protected String getResourceName(HttpServletRequest request) { return config.getTotalResourceName(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/BlockExceptionHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback; import com.alibaba.csp.sentinel.slots.block.BlockException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Handler for the blocked request. * * @author kaizi2009 */ public interface BlockExceptionHandler { /** * Handle the request when blocked. * * @param request Servlet request * @param response Servlet response * @param e the block exception * @throws Exception users may throw out the BlockException or other error occurs */ void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception; } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/DefaultBlockExceptionHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.StringUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * Default handler for the blocked request. * * @author kaizi2009 */ public class DefaultBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { // Return 429 (Too Many Requests) by default. response.setStatus(429); PrintWriter out = response.getWriter(); out.print("Blocked by Sentinel (flow limiting)"); out.flush(); out.close(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/RequestOriginParser.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback; import javax.servlet.http.HttpServletRequest; /** * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. * * @author kaizi2009 */ public interface RequestOriginParser { /** * Parse the origin from given HTTP request. * * @param request HTTP request * @return parsed origin */ String parseOrigin(HttpServletRequest request); } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/callback/UrlCleaner.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback; /** * Unify the resource target. * * @author kaizi2009 */ public interface UrlCleaner { /** * Unify the resource target. * * @param originUrl the original URL * @return the unified resource name */ String clean(String originUrl); } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; /** * Common base configuration for Spring Web MVC adapter. * * @author kaizi2009 * @since 1.7.1 */ public abstract class BaseWebMvcConfig { public final static String REQUEST_REF_EXCEPTION_NAME = "$$sentinel_spring_web_entry_attr-exception"; protected String requestAttributeName; protected String requestRefName; protected BlockExceptionHandler blockExceptionHandler; protected RequestOriginParser originParser; public String getRequestAttributeName() { return requestAttributeName; } public void setRequestAttributeName(String requestAttributeName) { this.requestAttributeName = requestAttributeName; this.requestRefName = this.requestAttributeName + "-rc"; } /** * Paired with attr name used to track reference count. * * @return */ public String getRequestRefName() { return requestRefName; } public BlockExceptionHandler getBlockExceptionHandler() { return blockExceptionHandler; } public void setBlockExceptionHandler(BlockExceptionHandler blockExceptionHandler) { this.blockExceptionHandler = blockExceptionHandler; } public RequestOriginParser getOriginParser() { return originParser; } public void setOriginParser(RequestOriginParser originParser) { this.originParser = originParser; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner; /** * @author kaizi2009 * @since 1.7.1 */ public class SentinelWebMvcConfig extends BaseWebMvcConfig { public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_entry_attr"; /** * Specify the URL cleaner that unifies the URL resources. */ private UrlCleaner urlCleaner; /** * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). */ private boolean httpMethodSpecify; /** * Specify whether unify web context(i.e. use the default context name), and is true by default. * * @since 1.7.2 */ private boolean webContextUnify = true; public SentinelWebMvcConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); } public UrlCleaner getUrlCleaner() { return urlCleaner; } public SentinelWebMvcConfig setUrlCleaner(UrlCleaner urlCleaner) { this.urlCleaner = urlCleaner; return this; } public boolean isHttpMethodSpecify() { return httpMethodSpecify; } public SentinelWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { this.httpMethodSpecify = httpMethodSpecify; return this; } public boolean isWebContextUnify() { return webContextUnify; } public SentinelWebMvcConfig setWebContextUnify(boolean webContextUnify) { this.webContextUnify = webContextUnify; return this; } @Override public String toString() { return "SentinelWebMvcConfig{" + "urlCleaner=" + urlCleaner + ", httpMethodSpecify=" + httpMethodSpecify + ", webContextUnify=" + webContextUnify + ", requestAttributeName='" + requestAttributeName + '\'' + ", blockExceptionHandler=" + blockExceptionHandler + ", originParser=" + originParser + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcTotalConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; /** * @author kaizi2009 * @since 1.7.1 */ public class SentinelWebMvcTotalConfig extends BaseWebMvcConfig { public static final String DEFAULT_TOTAL_RESOURCE_NAME = "spring-mvc-total-url-request"; public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_total_entry_attr"; private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME; public SentinelWebMvcTotalConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); } public String getTotalResourceName() { return totalResourceName; } public SentinelWebMvcTotalConfig setTotalResourceName(String totalResourceName) { this.totalResourceName = totalResourceName; return this; } @Override public String toString() { return "SentinelWebMvcTotalConfig{" + "totalResourceName='" + totalResourceName + '\'' + ", requestAttributeName='" + requestAttributeName + '\'' + ", blockExceptionHandler=" + blockExceptionHandler + ", originParser=" + originParser + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/ResultWrapper.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.fastjson.JSONObject; /** * @author kaizi2009 */ public class ResultWrapper { private Integer code; private String message; public ResultWrapper(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public static ResultWrapper error() { return new ResultWrapper(-1, "System error"); } public static ResultWrapper blocked() { return new ResultWrapper(-2, "Blocked by Sentinel"); } public String toJsonString() { return JSONObject.toJSONString(this); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.Collections; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; /** * @author kaizi2009 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc public class SentinelSpringMvcIntegrationTest { private static final String HELLO_STR = "Hello!"; @Autowired private MockMvc mvc; @Test public void testBase() throws Exception { String url = "/hello"; this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testAsync() throws Exception { String url = "/async"; this.mvc.perform(get(url)) .andExpect(status().isOk()); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertNull(ContextUtil.getContext()); } @Test public void testOriginParser() throws Exception { String springMvcPathVariableUrl = "/foo/{id}"; String limitOrigin = "userA"; final String headerName = "S-User"; configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin); // This will be passed since the caller is different: userB this.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) .andExpect(status().isOk()) .andExpect(content().string("foo 1")); // This will be blocked since the caller is same: userA this.mvc.perform( get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin)) .andExpect(status().isOk()) .andExpect(content().json(ResultWrapper.blocked().toJsonString())); // This will be passed since the caller is different: "" this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("foo 3")); FlowRuleManager.loadRules(null); } @Test public void testTotalInterceptor() throws Exception { String url = "/hello"; String totalTarget = "my_spring_mvc_total_url_request"; for (int i = 0; i < 3; i++) { this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); } ClusterNode cn = ClusterBuilderSlot.getClusterNode(totalTarget); assertNotNull(cn); assertEquals(3, cn.passQps(), 0.01); } @Test public void testRuntimeException() throws Exception { String url = "/runtimeException"; configureExceptionRulesFor(url, 3, null); int repeat = 3; for (int i = 0; i < repeat; i++) { this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.error().toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(i + 1, cn.passQps(), 0.01); } // This will be blocked and response json. this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.blocked().toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(repeat, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest(), 1); } @Test public void testExceptionPerception() throws Exception { String url = "/bizException"; configureExceptionDegradeRulesFor(url, 2.6, null); int repeat = 3; for (int i = 0; i < repeat; i++) { this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(new ResultWrapper(-1, "Biz error").toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(i + 1, cn.passQps(), 0.01); } // This will be blocked and response json. this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.blocked().toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(repeat, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest(), 1); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } private void configureExceptionRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } private void configureExceptionDegradeRulesFor(String resource, double count, String limitApp) { DegradeRule rule = new DegradeRule() .setCount(count) .setStatIntervalMs(1000) .setMinRequestAmount(1) .setTimeWindow(5) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } DegradeRuleManager.loadRules(Collections.singletonList(rule)); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptorTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class SentinelWebInterceptorTest { @Test(expected = IllegalArgumentException.class) public void testPassIllegalConfig() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setRequestAttributeName(null); SentinelWebInterceptor interceptor = new SentinelWebInterceptor(config); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/TestApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @author kaizi2009 */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelExceptionAware; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebTotalInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.servlet.http.HttpServletRequest; /** * Config sentinel interceptor * * @author kaizi2009 */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Bean public SentinelExceptionAware sentinelExceptionAware() { return new SentinelExceptionAware(); } @Override public void addInterceptors(InterceptorRegistry registry) { //Add sentinel interceptor addSpringMvcInterceptor(registry); //If you want to sentinel the total flow, you can add total interceptor addSpringMvcTotalInterceptor(registry); } private void addSpringMvcInterceptor(InterceptorRegistry registry) { //Config SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setBlockExceptionHandler((request, response, e) -> { String resourceName = e.getRule().getResource(); //Depending on your situation, you can choose to process or throw if ("/hello".equals(resourceName)) { //Do something ...... //Write string or json string; response.getWriter().write("/Blocked by sentinel"); } else { //Handle in global exception handling throw e; } }); //Custom configuration if necessary config.setHttpMethodSpecify(false); config.setWebContextUnify(true); config.setOriginParser(new RequestOriginParser() { @Override public String parseOrigin(HttpServletRequest request) { return request.getHeader("S-user"); } }); //Add sentinel interceptor registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { //Configure SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); //Custom configuration if necessary config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container"); config.setTotalResourceName("my_spring_mvc_total_url_request"); //Add sentinel interceptor registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.ResultWrapper; import com.alibaba.csp.sentinel.adapter.spring.webmvc.exception.BizException; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * Config 'BlockException' handler, handler it in spring veb 'ExceptionHandler' * * @author kaizi2009 */ @ControllerAdvice @Order(0) public class SentinelSpringMvcBlockHandlerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(BlockException.class) @ResponseBody public ResultWrapper sentinelBlockHandler(BlockException e) { AbstractRule rule = e.getRule(); //Log logger.info("Blocked by sentinel, {}", rule.toString()); //Return object return ResultWrapper.blocked(); } @ExceptionHandler(Exception.class) @ResponseBody public ResultWrapper exceptionHandler(Exception e) { logger.error("System error", e.getMessage()); return new ResultWrapper(-1, "System error"); } @ExceptionHandler(BizException.class) @ResponseBody public ResultWrapper bizExceptionHandler(BizException e) { logger.error("Biz error", e.getMessage()); return new ResultWrapper(-1, "Biz error"); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/controller/TestController.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc.controller; import com.alibaba.csp.sentinel.adapter.spring.webmvc.exception.BizException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; /** * @author kaizi2009 */ @RestController public class TestController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } @GetMapping("/err") public String apiError() { return "Oops..."; } @GetMapping("/foo/{id}") public String apiFoo(@PathVariable("id") Long id) { return "foo " + id; } @GetMapping("/runtimeException") public String runtimeException() { int i = 1 / 0; return "runtimeException"; } @GetMapping("/bizException") public String bizException() { throw new BizException(); } @GetMapping("/exclude/{id}") public String apiExclude(@PathVariable("id") Long id) { return "Exclude " + id; } @GetMapping("/async") @ResponseBody public DeferredResult distribute() throws Exception{ DeferredResult result = new DeferredResult<>(); Thread thread = new Thread(() -> result.setResult("async result.")); thread.start(); Thread.yield(); return result; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/exception/BizException.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.webmvc.exception; /** * @author lemonj */ public class BizException extends RuntimeException{ } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/README.md ================================================ # Sentinel Spring MVC Adapter ## Introduction Sentinel provides integration for Spring Web to enable flow control for web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-spring-webmvc-v6x-adapter x.y.z ``` Then we could add a configuration bean to configure the interceptor: ```java @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); // Enable the HTTP method prefix. config.setHttpMethodSpecify(true); // Add to the interceptor list. registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } } ``` Then Sentinel will extract URL patterns defined in Web Controller as the web resource (e.g. `/foo/{id}`). ## Configuration ### Block handling Sentinel Spring Web adapter provides a `BlockExceptionHandler` interface to handle the blocked requests. We could set the handler via `SentinelWebMvcTotalConfig#setBlockExceptionHandler()` method. By default the interceptor will throw out the `BlockException`. We need to set a global exception handler function in Spring to handle it. An example: ```java @ControllerAdvice @Order(0) public class SentinelBlockExceptionHandlerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(BlockException.class) @ResponseBody public String sentinelBlockHandler(BlockException e) { AbstractRule rule = e.getRule(); logger.info("Blocked by Sentinel: {}", rule.toString()); return "Blocked by Sentinel"; } } ``` We've provided a `DefaultBlockExceptionHandler`. When a request is blocked, the handler will return a default page indicating the request is rejected (`Blocked by Sentinel (flow limiting)`). The HTTP status code of the default block page is **429 (Too Many Requests)**. We could also implement our implementation of the `BlockExceptionHandler` interface and set to the config object. An example: ```java SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setBlockExceptionHandler((request, response, e) -> { String resourceName = e.getRule().getResource(); // Depending on your situation, you can choose to process or throw if ("/hello".equals(resourceName)) { // Do something ...... response.getWriter().write("Blocked by Sentinel"); } else { // Handle it in global exception handling throw e; } }); ``` ### Customized configuration - Common configuration in `SentinelWebMvcConfig` and `SentinelWebMvcTotalConfig`: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | `blockExceptionHandler` | The handler that handles the block request | `BlockExceptionHandler` | null (throw out the BlockException) | | `originParser` | Extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request | `RequestOriginParser` | - | - `SentinelWebMvcConfig` configuration: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. | `UrlCleaner` | - | | requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_entry_attr` | | httpMethodSpecify | Specify whether the URL resource name should contain the HTTP method prefix (e.g. `POST:`). | `boolean` | `false` | | webContextUnify | Specify whether unify web context(i.e. use the default context name). | `boolean` | `true` | - `SentinelWebMvcTotalConfig` configuration: | name | description | type | default value | | ------ | ------------ | ------ | ------- | | totalResourceName | The resource name in `SentinelTotalInterceptor` | `String` | `spring-mvc-total-url-request` | | requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_total_entry_attr` | ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-spring-webmvc-v6x-adapter jar 6.0.2 3.0.0 6.0.0 4.0.0 2.0.16 false com.alibaba.csp sentinel-core com.alibaba.csp sentinel-web-adapter-common jakarta.servlet jakarta.servlet-api ${servlet.api.version} provided org.springframework spring-webmvc ${spring.version} provided org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test org.apache.commons commons-lang3 3.14.0 test com.alibaba fastjson test junit junit test org.mockito mockito-inline test org.slf4j slf4j-api jakarta.xml.bind jakarta.xml.bind-api ${jakarta.xml.bind-api.version} org.slf4j slf4j-api ${slf4j-api.version} org.apache.maven.plugins maven-surefire-plugin ${maven.surefire.version} org.apache.maven.surefire surefire-junit47 3.2.5 ${skip.spring.v6x.test} ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/AbstractSentinelInterceptor.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.BaseWebMvcConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * Since request may be reprocessed in flow if any forwarding or including or other action * happened (see {@link jakarta.servlet.ServletRequest#getDispatcherType()}) we will only * deal with the initial request. So we use reference count to track in * dispatching "onion" though which we could figure out whether we are in initial type "REQUEST". * That means the sub-requests which we rarely meet in practice will NOT be recorded in Sentinel. *

* How to implement a forward sub-request in your action: *

 * initialRequest() {
 *     ModelAndView mav = new ModelAndView();
 *     mav.setViewName("another");
 *     return mav;
 * }
 * 
* * @since 1.8.8 */ public abstract class AbstractSentinelInterceptor implements AsyncHandlerInterceptor { public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context"; private static final String EMPTY_ORIGIN = ""; private final BaseWebMvcConfig baseWebMvcConfig; public AbstractSentinelInterceptor(BaseWebMvcConfig config) { AssertUtil.notNull(config, "BaseWebMvcConfig should not be null"); AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank"); this.baseWebMvcConfig = config; } /** * @param request * @param rcKey * @param step * @return reference count after increasing (initial value as zero to be increased) */ private Integer increaseReference(HttpServletRequest request, String rcKey, int step) { Object obj = request.getAttribute(rcKey); if (obj == null) { // initial obj = Integer.valueOf(0); } Integer newRc = (Integer) obj + step; request.setAttribute(rcKey, newRc); return newRc; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String resourceName = ""; try { resourceName = getResourceName(request); if (StringUtil.isEmpty(resourceName)) { return true; } if (increaseReference(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) { return true; } // Parse the request origin using registered origin parser. String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin); Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry); return true; } catch (BlockException e) { try { handleBlockException(request, response, resourceName, e); } finally { ContextUtil.exit(); } return false; } } /** * Return the resource name of the target web resource. * * @param request web request * @return the resource name of the target web resource. */ protected abstract String getResourceName(HttpServletRequest request); /** * Return the context name of the target web resource. * * @param request web request * @return the context name of the target web resource. */ protected String getContextName(HttpServletRequest request) { return SENTINEL_SPRING_WEB_CONTEXT_NAME; } /** * When a handler starts an asynchronous request, the DispatcherServlet exits without invoking postHandle and afterCompletion * Called instead of postHandle and afterCompletion to exit the context and clean thread-local variables when the handler is being executed concurrently. * * @param request the current request * @param response the current response * @param handler the handler (or {@link HandlerMethod}) that started async * execution, for type and/or instance examination */ @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { exit(request); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { exit(request, ex); } private void exit(HttpServletRequest request) { exit(request, null); } private void exit(HttpServletRequest request, Exception ex) { if (increaseReference(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) { return; } Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName()); if (entry == null) { // should not happen RecordLog.warn("[{}] No entry found in request, key: {}", getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName()); return; } // Record the status code here. // String resourceName = entry.getResourceWrapper().getName(); // int status = response.getStatus(); // StatusCodeMetricManager.getInstance().recordStatusCode(resourceName, status); traceExceptionAndExit(entry, ex); removeEntryInRequest(request); ContextUtil.exit(); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) { Object entryObject = request.getAttribute(attrKey); return entryObject == null ? null : (Entry) entryObject; } protected void removeEntryInRequest(HttpServletRequest request) { request.removeAttribute(baseWebMvcConfig.getRequestAttributeName()); } protected void traceExceptionAndExit(Entry entry, Exception ex) { if (entry != null) { if (ex != null) { Tracer.traceEntry(ex, entry); } entry.exit(); } } protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, resourceName, e); // Record status when blocked // int status = response.getStatus(); // StatusCodeMetricManager.getInstance().recordStatusCode(resourceName, status); } else { // Throw BlockException directly. Users need to handle it in Spring global exception handler. // NOTE: the status code statistics will be lost here! throw e; } } protected String parseOrigin(HttpServletRequest request) { String origin = EMPTY_ORIGIN; if (baseWebMvcConfig.getOriginParser() != null) { origin = baseWebMvcConfig.getOriginParser().parseOrigin(request); if (StringUtil.isEmpty(origin)) { return EMPTY_ORIGIN; } } return origin; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelWebInterceptor.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; import com.alibaba.csp.sentinel.util.StringUtil; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.servlet.HandlerMapping; /** * Spring Web MVC interceptor that integrates with Sentinel. *

* This will record resource as `${uri}`. * * @since 1.8.8 */ public class SentinelWebInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcConfig config; public SentinelWebInterceptor() { this(new SentinelWebMvcConfig()); } public SentinelWebInterceptor(SentinelWebMvcConfig config) { super(config); if (config == null) { // Use the default config by default. this.config = new SentinelWebMvcConfig(); } else { this.config = config; } } @Override protected String getResourceName(HttpServletRequest request) { // Resolve the Spring Web URL pattern from the request attribute. Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (resourceNameObject == null || !(resourceNameObject instanceof String)) { return null; } String resourceName = (String) resourceNameObject; UrlCleaner urlCleaner = config.getUrlCleaner(); if (urlCleaner != null) { resourceName = urlCleaner.clean(resourceName); } if (config.isContextPathSpecify() && request.getContextPath() != null) { resourceName = request.getContextPath() + resourceName; } if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) { resourceName = request.getMethod().toUpperCase() + ":" + resourceName; } return resourceName; } @Override protected String getContextName(HttpServletRequest request) { if (config.isWebContextUnify()) { return super.getContextName(request); } return getResourceName(request); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelWebPrefixInterceptor.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.util.StringUtil; import jakarta.servlet.http.HttpServletRequest; /** * Spring Web MVC interceptor that integrates with Sentinel. *

* This will record resource as `${httpMethod}:${uri}`. * * @since 1.8.8 */ public class SentinelWebPrefixInterceptor extends SentinelWebInterceptor { @Override protected String getResourceName(HttpServletRequest request) { String resourceName = super.getResourceName(request); // Add method specification if (StringUtil.isNotEmpty(resourceName)) { resourceName = request.getMethod().toUpperCase() + ":" + resourceName; } return resourceName; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelWebTotalInterceptor.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcTotalConfig; import jakarta.servlet.http.HttpServletRequest; /** * The web interceptor for all requests, which will unify all URL as * a single resource name (configured in {@link SentinelWebMvcTotalConfig}). * * @since 1.8.8 */ public class SentinelWebTotalInterceptor extends AbstractSentinelInterceptor { private final SentinelWebMvcTotalConfig config; public SentinelWebTotalInterceptor(SentinelWebMvcTotalConfig config) { super(config); if (config == null) { this.config = new SentinelWebMvcTotalConfig(); } else { this.config = config; } } public SentinelWebTotalInterceptor() { this(new SentinelWebMvcTotalConfig()); } @Override protected String getResourceName(HttpServletRequest request) { return config.getTotalResourceName(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/BlockExceptionHandler.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback; import com.alibaba.csp.sentinel.slots.block.BlockException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * Handler for the blocked request. * * @since 1.8.8 */ public interface BlockExceptionHandler { /** * Handle the request when blocked. * * @param request Servlet request * @param response Servlet response * @param resourceName resource name * @param e the block exception * @throws Exception users may throw out the BlockException or other error occurs */ void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e) throws Exception; } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandler.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback; import com.alibaba.csp.sentinel.slots.block.BlockException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * Default handler for the blocked request. * * @since 1.8.8 */ public class DefaultBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException ex) throws Exception { // Return 429 (Too Many Requests) by default. response.setStatus(429); PrintWriter out = response.getWriter(); out.print("Blocked by Sentinel (flow limiting)"); out.flush(); out.close(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/RequestOriginParser.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback; import jakarta.servlet.http.HttpServletRequest; /** * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. * * @since 1.8.8 */ public interface RequestOriginParser { /** * Parse the origin from given HTTP request. * * @param request HTTP request * @return parsed origin */ String parseOrigin(HttpServletRequest request); } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/BaseWebMvcConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.DefaultBlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; /** * Common base configuration for Spring Web MVC adapter. * * @since 1.8.8 */ public abstract class BaseWebMvcConfig { protected String requestAttributeName; protected String requestRefName; protected BlockExceptionHandler blockExceptionHandler = new DefaultBlockExceptionHandler(); protected RequestOriginParser originParser; public String getRequestAttributeName() { return requestAttributeName; } public void setRequestAttributeName(String requestAttributeName) { this.requestAttributeName = requestAttributeName; this.requestRefName = this.requestAttributeName + "-rc"; } /** * Paired with attr name used to track reference count. * * @return */ public String getRequestRefName() { return requestRefName; } public BlockExceptionHandler getBlockExceptionHandler() { return blockExceptionHandler; } public void setBlockExceptionHandler(BlockExceptionHandler blockExceptionHandler) { this.blockExceptionHandler = blockExceptionHandler; } public RequestOriginParser getOriginParser() { return originParser; } public void setOriginParser(RequestOriginParser originParser) { this.originParser = originParser; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelPreWebMvcConfig.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; /** * @since 1.8.8 */ public class SentinelPreWebMvcConfig extends BaseWebMvcConfig { public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_pre_spring_web_entry_attr"; private UrlCleaner urlCleaner; /** * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). */ private boolean httpMethodSpecify; /** * Specify whether unify web context(i.e. use the default context name), and is true by default. * * @since 1.7.2 */ private boolean webContextUnify = true; public SentinelPreWebMvcConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); } public boolean isHttpMethodSpecify() { return httpMethodSpecify; } public SentinelPreWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { this.httpMethodSpecify = httpMethodSpecify; return this; } public boolean isWebContextUnify() { return webContextUnify; } public SentinelPreWebMvcConfig setWebContextUnify(boolean webContextUnify) { this.webContextUnify = webContextUnify; return this; } public UrlCleaner getUrlCleaner() { return urlCleaner; } public SentinelPreWebMvcConfig setUrlCleaner(UrlCleaner urlCleaner) { this.urlCleaner = urlCleaner; return this; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelWebMvcConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; /** * @since 1.8.8 */ public class SentinelWebMvcConfig extends BaseWebMvcConfig { public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_entry_attr"; /** * Specify the URL cleaner that unifies the URL resources. */ private UrlCleaner urlCleaner; /** * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). */ private boolean httpMethodSpecify; /** * Specify whether unify web context(i.e. use the default context name), and is true by default. * * @since 1.7.2 */ private boolean webContextUnify = true; /** * Specify whether the URL resource name should contain the context-path */ private boolean contextPathSpecify = true; public SentinelWebMvcConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); try { String enableContextPath = System.getProperty("spring.cloud.ahas.sentinel.web.context-path", "true"); if (enableContextPath != null) { contextPathSpecify = Boolean.parseBoolean(enableContextPath); } } catch (Exception ignore) { } } public UrlCleaner getUrlCleaner() { return urlCleaner; } public SentinelWebMvcConfig setUrlCleaner(UrlCleaner urlCleaner) { this.urlCleaner = urlCleaner; return this; } public boolean isHttpMethodSpecify() { return httpMethodSpecify; } public SentinelWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { this.httpMethodSpecify = httpMethodSpecify; return this; } public boolean isWebContextUnify() { return webContextUnify; } public SentinelWebMvcConfig setWebContextUnify(boolean webContextUnify) { this.webContextUnify = webContextUnify; return this; } public boolean isContextPathSpecify() { return contextPathSpecify; } public SentinelWebMvcConfig setContextPathSpecify(boolean contextPathSpecify) { this.contextPathSpecify = contextPathSpecify; return this; } @Override public String toString() { return "SentinelWebMvcConfig{" + "urlCleaner=" + urlCleaner + ", httpMethodSpecify=" + httpMethodSpecify + ", webContextUnify=" + webContextUnify + ", contextPathSpecify=" + contextPathSpecify + ", requestAttributeName='" + requestAttributeName + '\'' + ", blockExceptionHandler=" + blockExceptionHandler + ", originParser=" + originParser + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelWebMvcTotalConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; /** * @since 1.8.8 */ public class SentinelWebMvcTotalConfig extends BaseWebMvcConfig { public static final String DEFAULT_TOTAL_RESOURCE_NAME = "spring-mvc-total-url-request"; public static final String DEFAULT_REQUEST_ATTRIBUTE_NAME = "$$sentinel_spring_web_total_entry_attr"; private String totalResourceName = DEFAULT_TOTAL_RESOURCE_NAME; public SentinelWebMvcTotalConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); } public String getTotalResourceName() { return totalResourceName; } public SentinelWebMvcTotalConfig setTotalResourceName(String totalResourceName) { this.totalResourceName = totalResourceName; return this; } @Override public String toString() { return "SentinelWebMvcTotalConfig{" + "totalResourceName='" + totalResourceName + '\'' + ", requestAttributeName='" + requestAttributeName + '\'' + ", blockExceptionHandler=" + blockExceptionHandler + ", originParser=" + originParser + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/WebServletLocalConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; /** * The configuration center for Web Servlet adapter (ported to Spring Web adapter). * * @since 1.8.8 */ public final class WebServletLocalConfig { public static final String BLOCK_PAGE_URL_CONF_KEY = "csp.sentinel.web.servlet.block.page"; public static final String BLOCK_PAGE_HTTP_STATUS_CONF_KEY = "csp.sentinel.web.servlet.block.status"; public static final String BLOCK_PAGE_ALLOW_ORIGINS_CONF_KEY = "csp.sentinel.web.servlet.block.cors-allow-origins"; private static final int HTTP_STATUS_TOO_MANY_REQUESTS = 429; /** * Get redirecting page when blocked by Sentinel. * * @return the block page URL, maybe null if not configured. */ public static String getBlockPage() { return SentinelConfig.getConfig(BLOCK_PAGE_URL_CONF_KEY); } public static void setBlockPage(String blockPage) { SentinelConfig.setConfig(BLOCK_PAGE_URL_CONF_KEY, blockPage); } /** *

Get the HTTP status when using the default block page.

*

You can set the status code with the {@code -Dcsp.sentinel.web.servlet.block.status} * property. When the property is empty or invalid, Sentinel will use 429 (Too Many Requests) * as the default status code.

* * @return the HTTP status of the default block page */ public static int getBlockPageHttpStatus() { String value = SentinelConfig.getConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY); if (StringUtil.isEmpty(value)) { return HTTP_STATUS_TOO_MANY_REQUESTS; } try { int s = Integer.parseInt(value); if (s <= 0) { throw new IllegalArgumentException("Invalid status code: " + s); } return s; } catch (Exception e) { RecordLog.warn("[WebServletConfig] Invalid block HTTP status (" + value + "), using default 429"); setBlockPageHttpStatus(HTTP_STATUS_TOO_MANY_REQUESTS); } return HTTP_STATUS_TOO_MANY_REQUESTS; } /** * Set the HTTP status of the default block page. * * @param httpStatus the HTTP status of the default block page */ public static void setBlockPageHttpStatus(int httpStatus) { if (httpStatus <= 0) { throw new IllegalArgumentException("Invalid HTTP status code: " + httpStatus); } SentinelConfig.setConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY, String.valueOf(httpStatus)); } private WebServletLocalConfig() {} } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/ResultWrapper.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.fastjson.JSONObject; /** * @author kaizi2009 */ public class ResultWrapper { private Integer code; private String message; public ResultWrapper(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public static ResultWrapper error() { return new ResultWrapper(-1, "System error"); } public static ResultWrapper blocked() { return new ResultWrapper(-2, "Blocked by Sentinel"); } public String toJsonString() { return JSONObject.toJSONString(this); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelSpringMvcIntegrationTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.Collections; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; /** * @author kaizi2009 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc public class SentinelSpringMvcIntegrationTest { private static final String HELLO_STR = "Hello!"; @Autowired private MockMvc mvc; @BeforeClass public static void disableMseHttpMethodPrefix() { System.setProperty("spring.cloud.mse.sentinel.web.http-method-prefix", "false"); } @Test public void testBase() throws Exception { String url = "/hello"; this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); } @Test public void testAsync() throws Exception { String url = "/async"; this.mvc.perform(get(url)) .andExpect(status().isOk()); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); assertNull(ContextUtil.getContext()); } @Test public void testOriginParser() throws Exception { String springMvcPathVariableUrl = "/foo/{id}"; String limitOrigin = "userA"; final String headerName = "S-User"; configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin); // This will be passed since the caller is different: userB this.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) .andExpect(status().isOk()) .andExpect(content().string("foo 1")); // This will be blocked since the caller is same: userA this.mvc.perform( get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin)) .andExpect(status().isOk()) .andExpect(content().json(ResultWrapper.blocked().toJsonString())); // This will be passed since the caller is different: "" this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string("foo 3")); FlowRuleManager.loadRules(null); } @Test public void testTotalInterceptor() throws Exception { String url = "/hello"; String totalTarget = "my_spring_mvc_total_url_request"; for (int i = 0; i < 3; i++) { this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); } ClusterNode cn = ClusterBuilderSlot.getClusterNode(totalTarget); assertNotNull(cn); assertEquals(3, cn.passQps(), 0.01); } @Test public void testRuntimeException() throws Exception { String url = "/runtimeException"; configureExceptionRulesFor(url, 3, null); int repeat = 3; for (int i = 0; i < repeat; i++) { this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.error().toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(i + 1, cn.passQps(), 0.01); } // This will be blocked and response json. this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.blocked().toJsonString())); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(repeat, cn.passQps(), 0.01); assertEquals(1, cn.blockRequest(), 1); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } private void configureExceptionRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelWebInterceptorHttpMethodPrefixTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; import jakarta.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.web.servlet.HandlerMapping; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; /** * The test for the resource request prefix concatenation in spring-webmvc-v6x. * * @author ylnxwlp */ public class SentinelWebInterceptorHttpMethodPrefixTest { private SentinelWebInterceptor interceptor; private HttpServletRequest mockRequest; @Before public void setUp() { mockRequest = Mockito.mock(HttpServletRequest.class); } @Test public void testGetResourceNameWithHttpMethodSpecifyEnabled() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn("/test/path"); when(mockRequest.getMethod()).thenReturn("POST"); when(mockRequest.getContextPath()).thenReturn(""); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("POST:/test/path", resourceName); } @Test public void testGetResourceNameWithHttpMethodSpecifyDisabled() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(false); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn("/no/prefix"); when(mockRequest.getMethod()).thenReturn("DELETE"); when(mockRequest.getContextPath()).thenReturn(""); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("/no/prefix", resourceName); } @Test public void testGetResourceNameEmptyResourceNameShouldReturnEmptyString() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn(""); when(mockRequest.getMethod()).thenReturn("PUT"); when(mockRequest.getContextPath()).thenReturn(""); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("", resourceName); } @Test public void testGetResourceNameNullResourceNameShouldReturnNull() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn(null); when(mockRequest.getMethod()).thenReturn("PATCH"); String resourceName = interceptor.getResourceName(mockRequest); assertNull(resourceName); } @Test public void testGetResourceNameWithUrlCleaner() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); config.setUrlCleaner(new UrlCleaner() { @Override public String clean(String originUrl) { return "/cleaned"; } }); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn("/dirty/path"); when(mockRequest.getMethod()).thenReturn("GET"); when(mockRequest.getContextPath()).thenReturn(""); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("GET:/cleaned", resourceName); } @Test public void testGetResourceNameWithContextPath() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); config.setContextPathSpecify(true); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn("/api/user"); when(mockRequest.getMethod()).thenReturn("GET"); when(mockRequest.getContextPath()).thenReturn("/myapp"); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("GET:/myapp/api/user", resourceName); } @Test public void testGetResourceNameWithContextPathDisabled() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setHttpMethodSpecify(true); config.setContextPathSpecify(false); interceptor = new SentinelWebInterceptor(config); when(mockRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)) .thenReturn("/api/user"); when(mockRequest.getMethod()).thenReturn("GET"); when(mockRequest.getContextPath()).thenReturn("/myapp"); String resourceName = interceptor.getResourceName(mockRequest); assertEquals("GET:/api/user", resourceName); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/SentinelWebInterceptorTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcConfig; import org.junit.Test; /** * @author Eric Zhao */ public class SentinelWebInterceptorTest { @Test(expected = IllegalArgumentException.class) public void testPassIllegalConfig() { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setRequestAttributeName(null); SentinelWebInterceptor interceptor = new SentinelWebInterceptor(config); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/TestApplication.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author kaizi2009 */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/callback/DefaultBlockExceptionHandlerTest.java ================================================ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import static org.junit.Assert.assertEquals; public class DefaultBlockExceptionHandlerTest { @Test public void handle_writeBlockPage() throws Exception { DefaultBlockExceptionHandler h = new DefaultBlockExceptionHandler(); MockHttpServletRequest req = new MockHttpServletRequest("GET", "/a/b/c"); req.setQueryString("a=1&b=2"); MockHttpServletResponse resp = new MockHttpServletResponse(); String resourceName = "/a/b/c"; BlockException ex = new FlowException("msg"); h.handle(req, resp, resourceName, ex); assertEquals(429, resp.getStatus()); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/InterceptorConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebTotalInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * Config sentinel interceptor * * @author kaizi2009 */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //Add sentinel interceptor addSpringMvcInterceptor(registry); //If you want to sentinel the total flow, you can add total interceptor addSpringMvcTotalInterceptor(registry); } private void addSpringMvcInterceptor(InterceptorRegistry registry) { //Config SentinelWebMvcConfig config = new SentinelWebMvcConfig(); config.setBlockExceptionHandler(new BlockExceptionHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e) throws Exception { resourceName = e.getRule().getResource(); //Depending on your situation, you can choose to process or throw if ("/hello".equals(resourceName)) { //Do something ...... //Write string or json string; response.getWriter().write("/Blocked by sentinel"); } else { //Handle in global exception handling throw e; } } }); //Custom configuration if necessary config.setHttpMethodSpecify(false); config.setWebContextUnify(true); config.setOriginParser(new RequestOriginParser() { @Override public String parseOrigin(HttpServletRequest request) { return request.getHeader("S-user"); } }); //Add sentinel interceptor registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { //Configure SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); //Custom configuration if necessary config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container"); config.setTotalResourceName("my_spring_mvc_total_url_request"); //Add sentinel interceptor registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/config/SentinelSpringMvcBlockHandlerConfig.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.ResultWrapper; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * Config 'BlockException' handler, handler it in spring veb 'ExceptionHandler' * * @author kaizi2009 */ @ControllerAdvice @Order(0) public class SentinelSpringMvcBlockHandlerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(BlockException.class) @ResponseBody public ResultWrapper sentinelBlockHandler(BlockException e) { AbstractRule rule = e.getRule(); //Log logger.info("Blocked by sentinel, {}", rule.toString()); //Return object return ResultWrapper.blocked(); } @ExceptionHandler(Exception.class) @ResponseBody public ResultWrapper exceptionHandler(Exception e) { logger.error("System error", e.getMessage()); return new ResultWrapper(-1, "System error"); } } ================================================ FILE: sentinel-adapter/sentinel-spring-webmvc-v6x-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc_v6x/controller/TestController.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; /** * @author kaizi2009 */ @RestController public class TestController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } @GetMapping("/err") public String apiError() { return "Oops..."; } @GetMapping("/foo/{id}") public String apiFoo(@PathVariable("id") Long id) { return "foo " + id; } @GetMapping("/runtimeException") public String runtimeException() { int i = 1 / 0; return "runtimeException"; } @GetMapping("/exclude/{id}") public String apiExclude(@PathVariable("id") Long id) { return "Exclude " + id; } @GetMapping("/async") @ResponseBody public DeferredResult distribute() throws Exception { DeferredResult result = new DeferredResult<>(); Thread thread = new Thread(() -> result.setResult("async result.")); thread.start(); Thread.yield(); return result; } } ================================================ FILE: sentinel-adapter/sentinel-web-adapter-common/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-web-adapter-common jar 8 8 junit junit test org.mockito mockito-core test org.assertj assertj-core test ================================================ FILE: sentinel-adapter/sentinel-web-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/web/common/UrlCleaner.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.web.common; /** * Unify the resource target. * * @since 1.8.8 */ public interface UrlCleaner { /** * Unify the resource target. * * @param originUrl the original URL * @return the unified resource name */ String clean(String originUrl); } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/README.md ================================================ # Sentinel Web Servlet Filter Sentinel provides Servlet filter integration to enable flow control for web requests. Add the following dependency in `pom.xml` (if you are using Maven): ```xml com.alibaba.csp sentinel-web-servlet x.y.z ``` To activate the filter, you can simply configure your `web.xml` with: ```xml SentinelCommonFilter com.alibaba.csp.sentinel.adapter.servlet.CommonFilter SentinelCommonFilter /* ``` For Spring web applications you can configure with Spring bean: ```java @Configuration public class FilterConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(new CommonFilter()); // Set the matching URL pattern for the filter. registration.addUrlPatterns("/*"); registration.setName("sentinelCommonFilter"); registration.setOrder(1); // Set whether to support the specified HTTP method prefix for the filter. registration.addInitParameter(CommonFilter.HTTP_METHOD_SPECIFY, "false"); return registration; } } ``` When a request is blocked, Sentinel servlet filter will display a default page indicating the request is rejected. The HTTP status code of the default block page is **429 (Too Many Requests)**. You can customize it via the `csp.sentinel.web.servlet.block.status` configuration item (since 1.7.0). If customized block page is set (via `WebServletConfig.setBlockPage(blockPage)` method), the filter will redirect the request to provided URL. You can also implement your own block handler (the `UrlBlockHandler` interface) and register to `WebCallbackManager`. The `UrlCleaner` interface is designed for clean and unify the URL resource. For REST APIs, you have to clean the URL resource (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or the amount of context and resources will exceed the threshold. If you need to exclude some URLs (that should not be recorded as Sentinel resources), you could also leverage the `UrlCleaner` interface. You may unify the unwanted URLs to the empty string `""` or `null`, then the URLs will be excluded (since Sentinel 1.6.3). The `RequestOriginParser` interface is useful for extracting request origin (e.g. IP or appName from HTTP Header) from HTTP request. You can implement your own `RequestOriginParser` and register to `WebCallbackManager`. ================================================ FILE: sentinel-adapter/sentinel-web-servlet/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-adapter ${revision} ../pom.xml sentinel-web-servlet jar 3.1.0 2.4.13 com.alibaba.csp sentinel-core javax.servlet javax.servlet-api ${servlet.api.version} provided junit junit test org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.StringUtil; /** * Servlet filter that integrates with Sentinel. * * @author youji.zj * @author Eric Zhao * @author zhaoyuguang */ public class CommonFilter implements Filter { /** * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). */ public static final String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY"; /** * If enabled, use the default context name, or else use the URL path as the context name, * {@link WebServletConfig#WEB_SERVLET_CONTEXT_NAME}. Please pay attention to the number of context (EntranceNode), * which may affect the memory footprint. * * @since 1.7.0 */ public static final String WEB_CONTEXT_UNIFY = "WEB_CONTEXT_UNIFY"; private final static String COLON = ":"; private boolean httpMethodSpecify = false; private boolean webContextUnify = true; @Override public void init(FilterConfig filterConfig) { httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter(HTTP_METHOD_SPECIFY)); if (filterConfig.getInitParameter(WEB_CONTEXT_UNIFY) != null) { webContextUnify = Boolean.parseBoolean(filterConfig.getInitParameter(WEB_CONTEXT_UNIFY)); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest sRequest = (HttpServletRequest) request; Entry urlEntry = null; try { String target = FilterUtil.filterTarget(sRequest); // Clean and unify the URL. // For REST APIs, you have to clean the URL (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or // the amount of context and resources will exceed the threshold. UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner(); if (urlCleaner != null) { target = urlCleaner.clean(target); } // If you intend to exclude some URLs, you can convert the URLs to the empty string "" // in the UrlCleaner implementation. if (!StringUtil.isEmpty(target)) { // Parse the request origin using registered origin parser. String origin = parseOrigin(sRequest); String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target; ContextUtil.enter(contextName, origin); if (httpMethodSpecify) { // Add HTTP method prefix if necessary. String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target; urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN); } else { urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN); } } chain.doFilter(request, response); } catch (BlockException e) { HttpServletResponse sResponse = (HttpServletResponse) response; // Return the block page, or redirect to another URL. WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e); } catch (IOException | ServletException | RuntimeException e2) { Tracer.traceEntry(e2, urlEntry); throw e2; } finally { if (urlEntry != null) { urlEntry.exit(); } ContextUtil.exit(); } } private String parseOrigin(HttpServletRequest request) { RequestOriginParser originParser = WebCallbackManager.getRequestOriginParser(); String origin = EMPTY_ORIGIN; if (originParser != null) { origin = originParser.parseOrigin(request); if (StringUtil.isEmpty(origin)) { return EMPTY_ORIGIN; } } return origin; } @Override public void destroy() { } private static final String EMPTY_ORIGIN = ""; } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; /*** * Servlet filter for all requests. * * @author youji.zj */ public class CommonTotalFilter implements Filter { public static final String TOTAL_URL_REQUEST = "total-url-request"; @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest sRequest = (HttpServletRequest)request; Entry entry = null; try { ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME); entry = SphU.entry(TOTAL_URL_REQUEST, ResourceTypeConstants.COMMON_WEB); chain.doFilter(request, response); } catch (BlockException e) { HttpServletResponse sResponse = (HttpServletResponse)response; WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e); } catch (IOException | ServletException | RuntimeException e2) { Tracer.trace(e2); throw e2; } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } } @Override public void destroy() { } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; /*** * The default {@link UrlBlockHandler}. * * @author youji.zj */ public class DefaultUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException { // Directly redirect to the default flow control (blocked) page or customized block page. FilterUtil.blockRequest(request, response); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; /*** * @author youji.zj */ public class DefaultUrlCleaner implements UrlCleaner { @Override public String clean(String originUrl) { return originUrl; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/RequestOriginParser.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; import javax.servlet.http.HttpServletRequest; /** * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. * * @author Eric Zhao * @since 0.2.0 */ public interface RequestOriginParser { /** * Parse the origin from given HTTP request. * * @param request HTTP request * @return parsed origin */ String parseOrigin(HttpServletRequest request); } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; /*** * The URL block handler handles requests when blocked. * * @author youji.zj */ public interface UrlBlockHandler { /** * Handle the request when blocked. * * @param request Servlet request * @param response Servlet response * @param ex the block exception. * @throws IOException some error occurs */ void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException; } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; /*** * @author youji.zj */ public interface UrlCleaner { /*** *

Process the url. Some path variables should be handled and unified.

*

e.g. collect_item_relation--10200012121-.html will be converted to collect_item_relation.html

* * @param originUrl original url * @return processed url */ String clean(String originUrl); } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.callback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Registry for URL cleaner and URL block handler. * * @author youji.zj */ public class WebCallbackManager { /** * URL cleaner. */ private static volatile UrlCleaner urlCleaner = new DefaultUrlCleaner(); /** * URL block handler. */ private static volatile UrlBlockHandler urlBlockHandler = new DefaultUrlBlockHandler(); private static volatile RequestOriginParser requestOriginParser = null; public static UrlCleaner getUrlCleaner() { return urlCleaner; } public static void setUrlCleaner(UrlCleaner urlCleaner) { WebCallbackManager.urlCleaner = urlCleaner; } public static UrlBlockHandler getUrlBlockHandler() { return urlBlockHandler; } public static void setUrlBlockHandler(UrlBlockHandler urlBlockHandler) { AssertUtil.isTrue(urlBlockHandler != null, "URL block handler should not be null"); WebCallbackManager.urlBlockHandler = urlBlockHandler; } public static RequestOriginParser getRequestOriginParser() { return requestOriginParser; } public static void setRequestOriginParser(RequestOriginParser requestOriginParser) { WebCallbackManager.requestOriginParser = requestOriginParser; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.config; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import com.alibaba.csp.sentinel.adapter.servlet.CommonTotalFilter; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; /** * The configuration center for Web Servlet adapter. * * @author leyou * @author zhaoyuguang */ public final class WebServletConfig { public static final String WEB_SERVLET_CONTEXT_NAME = "sentinel_web_servlet_context"; public static final String BLOCK_PAGE_URL_CONF_KEY = "csp.sentinel.web.servlet.block.page"; public static final String BLOCK_PAGE_HTTP_STATUS_CONF_KEY = "csp.sentinel.web.servlet.block.status"; private static final int HTTP_STATUS_TOO_MANY_REQUESTS = 429; /** * Get redirecting page when Sentinel blocking for {@link CommonFilter} or * {@link CommonTotalFilter} occurs. * * @return the block page URL, maybe null if not configured. */ public static String getBlockPage() { return SentinelConfig.getConfig(BLOCK_PAGE_URL_CONF_KEY); } public static void setBlockPage(String blockPage) { SentinelConfig.setConfig(BLOCK_PAGE_URL_CONF_KEY, blockPage); } /** *

Get the HTTP status when using the default block page.

*

You can set the status code with the {@code -Dcsp.sentinel.web.servlet.block.status} * property. When the property is empty or invalid, Sentinel will use 429 (Too Many Requests) * as the default status code.

* * @return the HTTP status of the default block page * @since 1.7.0 */ public static int getBlockPageHttpStatus() { String value = SentinelConfig.getConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY); if (StringUtil.isEmpty(value)) { return HTTP_STATUS_TOO_MANY_REQUESTS; } try { int s = Integer.parseInt(value); if (s <= 0) { throw new IllegalArgumentException("Invalid status code: " + s); } return s; } catch (Exception e) { RecordLog.warn("[WebServletConfig] Invalid block HTTP status (" + value + "), using default 429"); setBlockPageHttpStatus(HTTP_STATUS_TOO_MANY_REQUESTS); } return HTTP_STATUS_TOO_MANY_REQUESTS; } /** * Set the HTTP status of the default block page. * * @param httpStatus the HTTP status of the default block page * @since 1.7.0 */ public static void setBlockPageHttpStatus(int httpStatus) { if (httpStatus <= 0) { throw new IllegalArgumentException("Invalid HTTP status code: " + httpStatus); } SentinelConfig.setConfig(BLOCK_PAGE_HTTP_STATUS_CONF_KEY, String.valueOf(httpStatus)); } private WebServletConfig() {} } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet.util; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.util.StringUtil; /** * Util class for web servlet filter. * * @author zhaoyuguang * @author youji.zj * @author Eric Zhao */ public final class FilterUtil { private static final String PATH_SPLIT = "/"; public static String filterTarget(HttpServletRequest request) { String pathInfo = getResourcePath(request); if (!pathInfo.startsWith(PATH_SPLIT)) { pathInfo = PATH_SPLIT + pathInfo; } if (PATH_SPLIT.equals(pathInfo)) { return pathInfo; } // Note: pathInfo should be converted to camelCase style. int lastSlashIndex = pathInfo.lastIndexOf("/"); if (lastSlashIndex >= 0) { pathInfo = pathInfo.substring(0, lastSlashIndex) + "/" + StringUtil.trim(pathInfo.substring(lastSlashIndex + 1)); } else { pathInfo = PATH_SPLIT + StringUtil.trim(pathInfo); } return pathInfo; } public static void blockRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { StringBuffer url = request.getRequestURL(); if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) { url.append("?").append(request.getQueryString()); } if (StringUtil.isBlank(WebServletConfig.getBlockPage())) { writeDefaultBlockedPage(response, WebServletConfig.getBlockPageHttpStatus()); } else { String redirectUrl = WebServletConfig.getBlockPage() + "?http_referer=" + url.toString(); // Redirect to the customized block page. response.sendRedirect(redirectUrl); } } private static void writeDefaultBlockedPage(HttpServletResponse response, int httpStatus) throws IOException { response.setStatus(httpStatus); PrintWriter out = response.getWriter(); out.print(DEFAULT_BLOCK_MSG); out.flush(); out.close(); } private static String getResourcePath(HttpServletRequest request) { String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false); String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0); return servletPath + pathInfo; } private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException { return normalizePath(path, true, false, removeTrailingSlash); } private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative, boolean removeTrailingSlash) throws IllegalStateException { char[] pathChars = StringUtil.trimToEmpty(path).toCharArray(); int length = pathChars.length; // Check path and slash. boolean startsWithSlash = false; boolean endsWithSlash = false; if (length > 0) { char firstChar = pathChars[0]; char lastChar = pathChars[length - 1]; startsWithSlash = firstChar == PATH_SPLIT.charAt(0) || firstChar == '\\'; endsWithSlash = lastChar == PATH_SPLIT.charAt(0) || lastChar == '\\'; } StringBuilder buf = new StringBuilder(length); boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash; int index = startsWithSlash ? 0 : -1; int level = 0; if (isAbsolutePath) { buf.append(PATH_SPLIT); } while (index < length) { index = indexOfSlash(pathChars, index + 1, false); if (index == length) { break; } int nextSlashIndex = indexOfSlash(pathChars, index, true); String element = new String(pathChars, index, nextSlashIndex - index); index = nextSlashIndex; // Ignore "." if (".".equals(element)) { continue; } // Backtrack ".." if ("..".equals(element)) { if (level == 0) { if (isAbsolutePath) { throw new IllegalStateException(path); } else { buf.append("..").append(PATH_SPLIT); } } else { buf.setLength(pathChars[--level]); } continue; } pathChars[level++] = (char)buf.length(); buf.append(element).append(PATH_SPLIT); } // remove the last "/" if (buf.length() > 0) { if (!endsWithSlash || removeTrailingSlash) { buf.setLength(buf.length() - 1); } } return buf.toString(); } private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) { int i = beginIndex; for (; i < chars.length; i++) { char ch = chars[i]; if (slash) { if (ch == PATH_SPLIT.charAt(0) || ch == '\\') { break; // if a slash } } else { if (ch != PATH_SPLIT.charAt(0) && ch != '\\') { break; // if not a slash } } } return i; } public static final String DEFAULT_BLOCK_MSG = "Blocked by Sentinel (flow limiting)"; private FilterUtil() {} } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import java.util.Collections; import javax.servlet.http.HttpServletRequest; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlCleaner; import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author zhaoyuguang * @author Eric Zhao */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc public class CommonFilterTest { private static final String HELLO_STR = "Hello!"; @Autowired private MockMvc mvc; private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testCommonFilterMiscellaneous() throws Exception { Constants.ROOT.removeChildList(); String url = "/hello"; this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals("", context); testCommonBlockAndRedirectBlockPage(url, cn); // Test for url cleaner. testUrlCleaner(); testUrlExclusion(); testCustomOriginParser(); } private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cn) throws Exception { configureRulesFor(url, 0); // The request will be blocked and response is default block message. WebServletConfig.setBlockPageHttpStatus(HttpStatus.OK.value()); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); assertEquals(1, cn.blockQps(), 0.01); WebServletConfig.setBlockPageHttpStatus(HttpStatus.TOO_MANY_REQUESTS.value()); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isTooManyRequests()) .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); // Test for redirect. String redirectUrl = "http://some-location.com"; WebServletConfig.setBlockPage(redirectUrl); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", redirectUrl + "?http_referer=http://localhost/hello")); FlowRuleManager.loadRules(null); WebServletConfig.setBlockPage(""); } private void testUrlCleaner() throws Exception { final String fooPrefix = "/foo/"; String url1 = fooPrefix + 1; String url2 = fooPrefix + 2; WebCallbackManager.setUrlCleaner(new UrlCleaner() { @Override public String clean(String originUrl) { if (originUrl.startsWith(fooPrefix)) { return "/foo/*"; } return originUrl; } }); this.mvc.perform(get(url1).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andExpect(content().string("Hello 1")); this.mvc.perform(get(url2).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andExpect(content().string("Hello 2")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(fooPrefix + "*"); assertEquals(2, cn.passQps(), 0.01); assertNull(ClusterBuilderSlot.getClusterNode(url1)); assertNull(ClusterBuilderSlot.getClusterNode(url2)); WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner()); } private void testUrlExclusion() throws Exception { final String excludePrefix = "/exclude/"; String url = excludePrefix + 1; WebCallbackManager.setUrlCleaner(new UrlCleaner() { @Override public String clean(String originUrl) { if(originUrl.startsWith(excludePrefix)) { return ""; } return originUrl; } }); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andExpect(content().string("Exclude 1")); assertNull(ClusterBuilderSlot.getClusterNode(url)); WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner()); } private void testCustomOriginParser() throws Exception { String url = "/hello"; String limitOrigin = "userA"; final String headerName = "S-User"; configureRulesFor(url, 0, limitOrigin); WebCallbackManager.setRequestOriginParser(new RequestOriginParser() { @Override public String parseOrigin(HttpServletRequest request) { String origin = request.getHeader(headerName); return origin != null ? origin : ""; } }); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); // This will be blocked. this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, limitOrigin)) .andExpect(status().isTooManyRequests()) .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); WebCallbackManager.setRequestOriginParser(null); FlowRuleManager.loadRules(null); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/FilterConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration public class FilterConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Eric Zhao */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/TestController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servlet; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao */ @RestController public class TestController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } @GetMapping("/err") public String apiError() { return "Oops..."; } @GetMapping("/foo/{id}") public String apiFoo(@PathVariable("id") Long id) { return "Hello " + id; } @GetMapping("/exclude/{id}") public String apiExclude(@PathVariable("id") Long id) { return "Exclude " + id; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletcontext/CommonFilterContextTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletcontext; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author zhaoyuguang */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestContextApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class CommonFilterContextTest { private static final String HELLO_STR = "Hello!"; @Autowired private MockMvc mvc; private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testCommonFilterMiscellaneous() throws Exception { String url = "/hello"; this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); assertNotNull(cn); assertEquals(1, cn.passQps(), 0.01); String context = ""; for (Node n : Constants.ROOT.getChildList()) { if (n instanceof EntranceNode) { String id = ((EntranceNode) n).getId().getName(); if (url.equals(id)) { context = ((EntranceNode) n).getId().getName(); } } } assertEquals(url, context); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletcontext/FilterContextConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletcontext; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author zhaoyuguang */ @Configuration public class FilterContextConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletcontext/TestContextApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletcontext; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author zhaoyuguang */ @SpringBootApplication public class TestContextApplication { public static void main(String[] args) { SpringApplication.run(TestContextApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletcontext/TestContextController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletcontext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhaoyuguang */ @RestController public class TestContextController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletmethod; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import java.util.Collections; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author zhaoyuguang * @author Roger Law */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class CommonFilterMethodTest { private static final String HELLO_STR = "Hello!"; private static final String HELLO_POST_STR = "Hello Post!"; private static final String GET = "GET"; private static final String POST = "POST"; private static final String COLON = ":"; @Autowired private MockMvc mvc; private void configureRulesFor(String resource, int count) { configureRulesFor(resource, count, "default"); } private void configureRulesFor(String resource, int count, String limitApp) { FlowRule rule = new FlowRule() .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setResource(resource); if (StringUtil.isNotBlank(limitApp)) { rule.setLimitApp(limitApp); } FlowRuleManager.loadRules(Collections.singletonList(rule)); } @Test public void testCommonFilterMiscellaneous() throws Exception { String url = "/hello"; this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_STR)); ClusterNode cnGet = ClusterBuilderSlot.getClusterNode(GET + COLON + url); assertNotNull(cnGet); assertEquals(1, cnGet.passQps(), 0.01); ClusterNode cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url); assertNull(cnPost); this.mvc.perform(post(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_POST_STR)); cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url); assertNotNull(cnPost); assertEquals(1, cnPost.passQps(), 0.01); testCommonBlockAndRedirectBlockPage(url, cnGet, cnPost); } private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cnGet, ClusterNode cnPost) throws Exception { configureRulesFor(GET + ":" + url, 0); // The request will be blocked and response is default block message. this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) .andExpect(status().isTooManyRequests()) .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); assertEquals(1, cnGet.blockQps(), 0.01); // Test for post pass this.mvc.perform(post(url)) .andExpect(status().isOk()) .andExpect(content().string(HELLO_POST_STR)); assertEquals(2, cnPost.passQps(), 0.01); FlowRuleManager.loadRules(null); WebServletConfig.setBlockPage(""); } @After public void cleanUp() { FlowRuleManager.loadRules(null); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java ================================================ package com.alibaba.csp.sentinel.adapter.servletmethod; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author: Roger Law **/ @Configuration public class FilterMethodConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter(CommonFilter.HTTP_METHOD_SPECIFY, "true"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletmethod; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Eric Zhao */ @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } ================================================ FILE: sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.servletmethod; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Roger Law */ @RestController public class TestMethodController { @GetMapping("/hello") public String apiHello() { return "Hello!"; } @PostMapping("/hello") public String apiHelloPost() { return "Hello Post!"; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/README.md ================================================ # Sentinel Zuul Adapter Sentinel Zuul Adapter provides **route level** and **customized API level** flow control for Zuul API Gateway. > *Note*: this adapter only support Zuul 1.x. ## How to use 1. Add Maven dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-zuul-adapter x.y.z ``` 2. Register filters For Spring Cloud Zuul users, we only need to inject the three filters in Spring configuration class like this: ```java @Configuration public class ZuulConfig { @Bean public ZuulFilter sentinelZuulPreFilter() { // We can provider the filter order here. return new SentinelZuulPreFilter(10000); } @Bean public ZuulFilter sentinelZuulPostFilter() { return new SentinelZuulPostFilter(1000); } @Bean public ZuulFilter sentinelZuulErrorFilter() { return new SentinelZuulErrorFilter(-1); } } ``` For original Zuul users: ```java // Get filter registry final FilterRegistry r = FilterRegistry.instance(); // We need to register all three filters. SentinelZuulPreFilter sentinelPreFilter = new SentinelZuulPreFilter(); r.put("sentinelZuulPreFilter", sentinelPreFilter); SentinelZuulPostFilter postFilter = new SentinelZuulPostFilter(); r.put("sentinelZuulPostFilter", postFilter); SentinelZuulErrorFilter errorFilter = new SentinelZuulErrorFilter(); r.put("sentinelZuulErrorFilter", errorFilter); ``` ## How it works As Zuul run as per thread per connection block model, we add filters around route filter to trace Sentinel statistics. - `SentinelZuulPreFilter`: This pre-filter will regard all proxy ID (`proxy` in `RequestContext`) and all customized API as resources. When a `BlockException` caught, the filter will try to find a fallback to execute. - `SentinelZuulPostFilter`: When the response has no exception caught, the post filter will complete the entries. - `SentinelZuulErrorFilter`: When an exception is caught, the filter will trace the exception and complete the entries. sentinel zuul The order of filters can be changed via the constructor. The invocation chain resembles this: ```bash -EntranceNode: sentinel_gateway_context$$route$$another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:8 1mb:1 1mt:9) --another-route-b(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:1 1mt:5) --another_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:4 1mb:0 1mt:4) -EntranceNode: sentinel_gateway_context$$route$$my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:6 1mb:0 1mt:6) --my-route-1(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2) --some_customized_api(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:2 1mb:0 1mt:2) ``` ## Integration with Sentinel Dashboard 1. Start [Sentinel Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard). 2. You can configure the rules in Sentinel dashboard or via dynamic rule configuration. ## Fallbacks You can implement `SentinelFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown. The default fallback provider is `DefaultBlockFallbackProvider`. By default fallback route is proxy ID (or customized API name). Here is an example: ```java // custom provider public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider { private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class); // you can define root as service level @Override public String getRoute() { return "my-route"; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route)); if (cause instanceof BlockException) { return new BlockResponse(429, "Sentinel block exception", route); } else { return new BlockResponse(500, "System Error", route); } } } // register fallback ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider()); ``` Default block response: ```json { "code":429, "message":"Sentinel block exception", "route":"/" } ``` ## Request origin parser You can register customized request origin parser like this: ```java public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { return request.getRemoteAddr(); } } ``` ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-zuul-adapter jar 1.3.1 3.1.0 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-api-gateway-adapter-common javax.servlet javax.servlet-api ${servlet.api.version} provided com.netflix.zuul zuul-core ${zuul.version} provided org.mockito mockito-all org.springframework spring-core 4.3.20.RELEASE provided org.springframework.cloud spring-cloud-starter-netflix-zuul 1.4.6.RELEASE test junit junit test org.mockito mockito-core test org.springframework spring-test 4.3.20.RELEASE test ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/RequestContextItemParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul; import javax.servlet.http.Cookie; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.netflix.zuul.context.RequestContext; /** * @author Eric Zhao * @since 1.6.0 */ public class RequestContextItemParser implements RequestItemParser { @Override public String getPath(RequestContext requestContext) { return requestContext.getRequest().getServletPath(); } @Override public String getRemoteAddress(RequestContext requestContext) { return requestContext.getRequest().getRemoteAddr(); } @Override public String getHeader(RequestContext requestContext, String headerKey) { return requestContext.getRequest().getHeader(headerKey); } @Override public String getUrlParam(RequestContext requestContext, String paramName) { return requestContext.getRequest().getParameter(paramName); } @Override public String getCookieValue(RequestContext requestContext, String cookieName) { Cookie[] cookies = requestContext.getRequest().getCookies(); if (cookies == null || cookieName == null) { return null; } for (Cookie cookie : cookies) { if (cookie != null && cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/ZuulApiDefinitionChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver; /** * @author Eric Zhao * @since 1.6.0 */ public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver { @Override public void onChange(Set apiDefinitions) { ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/ZuulGatewayApiMatcherManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.matcher.RequestContextApiMatcher; /** * @author Eric Zhao * @since 1.6.0 */ public final class ZuulGatewayApiMatcherManager { private static final Map API_MATCHER_MAP = new ConcurrentHashMap<>(); public static Map getApiMatcherMap() { return Collections.unmodifiableMap(API_MATCHER_MAP); } public static RequestContextApiMatcher getMatcher(final String apiName) { if (apiName == null) { return null; } return API_MATCHER_MAP.get(apiName); } public static Set getApiDefinitionSet() { Set set = new HashSet<>(); for (RequestContextApiMatcher matcher : API_MATCHER_MAP.values()) { set.add(matcher.getApiDefinition()); } return set; } static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) { if (definitions == null || definitions.isEmpty()) { API_MATCHER_MAP.clear(); return; } for (ApiDefinition definition : definitions) { addApiDefinition(definition); } } static void addApiDefinition(ApiDefinition definition) { API_MATCHER_MAP.put(definition.getApiName(), new RequestContextApiMatcher(definition)); } private ZuulGatewayApiMatcherManager() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/matcher/RequestContextApiMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api.matcher; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route.ZuulRouteMatchers; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.context.RequestContext; /** * @author Eric Zhao * @since 1.6.0 */ public class RequestContextApiMatcher extends AbstractApiMatcher { public RequestContextApiMatcher(ApiDefinition apiDefinition) { super(apiDefinition); } @Override protected void initializeMatchers() { if (apiDefinition.getPredicateItems() != null) { for (ApiPredicateItem item : apiDefinition.getPredicateItems()) { Predicate predicate = fromApiPredicate(item); if (predicate != null) { matchers.add(predicate); } } } } private Predicate fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { if (item instanceof ApiPathPredicateItem) { return fromApiPathPredicate((ApiPathPredicateItem)item); } return null; } private Predicate fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) { String pattern = item.getPattern(); if (StringUtil.isBlank(pattern)) { return null; } switch (item.getMatchStrategy()) { case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX: return ZuulRouteMatchers.regexPath(pattern); case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX: return ZuulRouteMatchers.antPath(pattern); default: return ZuulRouteMatchers.exactPath(pattern); } } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/PrefixRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.context.RequestContext; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import javax.servlet.http.HttpServletRequest; /** * @author Eric Zhao * @since 1.6.0 */ public class PrefixRoutePathMatcher implements Predicate { private final String pattern; private final PathMatcher pathMatcher; private final boolean canMatch; public PrefixRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.pathMatcher = new AntPathMatcher(); this.canMatch = pathMatcher.isPattern(pattern); } @Override public boolean test(RequestContext context) { //Solve the problem of prefix matching HttpServletRequest request = context.getRequest(); String path = request.getRequestURI(); if (path == null) { AssertUtil.assertNotBlank(pattern, "requesturi cannot be blank"); } if (canMatch) { return pathMatcher.match(pattern, path); } return false; } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/RegexRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.context.RequestContext; import javax.servlet.http.HttpServletRequest; import java.util.regex.Pattern; /** * @author Eric Zhao * @since 1.6.0 */ public class RegexRoutePathMatcher implements Predicate { private final String pattern; private final Pattern regex; public RegexRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.regex = Pattern.compile(pattern); } @Override public boolean test(RequestContext context) { //Solve the problem of route matching HttpServletRequest request = context.getRequest(); String path = request.getRequestURI(); if (path == null) { AssertUtil.assertNotBlank(pattern, "requesturi cannot be blank"); } return regex.matcher(path).matches(); } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/ZuulRouteMatchers.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.context.RequestContext; /** * @author Eric Zhao * @since 1.6.0 */ public final class ZuulRouteMatchers { public static Predicate all() { return new Predicate() { @Override public boolean test(RequestContext requestContext) { return true; } }; } public static Predicate antPath(String pathPattern) { return new PrefixRoutePathMatcher(pathPattern); } public static Predicate exactPath(final String path) { return new Predicate() { @Override public boolean test(RequestContext exchange) { return exchange.getRequest().getServletPath().equals(path); } }; } public static Predicate regexPath(String pathPattern) { return new RegexRoutePathMatcher(pathPattern); } private ZuulRouteMatchers() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/callback/DefaultRequestOriginParser.java ================================================ package com.alibaba.csp.sentinel.adapter.gateway.zuul.callback; import javax.servlet.http.HttpServletRequest; /** * @author tiger */ public class DefaultRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { return ""; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/callback/RequestOriginParser.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.callback; import javax.servlet.http.HttpServletRequest; /** * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. * * @author tiger */ public interface RequestOriginParser { /** * Parse the origin from given HTTP request. * * @param request HTTP request * @return parsed origin */ String parseOrigin(HttpServletRequest request); } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/callback/ZuulGatewayCallbackManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.callback; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.6.0 */ public final class ZuulGatewayCallbackManager { private static volatile RequestOriginParser originParser = new DefaultRequestOriginParser(); public static RequestOriginParser getOriginParser() { return originParser; } public static void setOriginParser(RequestOriginParser originParser) { AssertUtil.notNull(originParser, "originParser cannot be null"); ZuulGatewayCallbackManager.originParser = originParser; } private ZuulGatewayCallbackManager() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/constants/ZuulConstant.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.constants; import com.netflix.zuul.ZuulFilter; /** * @author tiger */ public class ZuulConstant { /** * Zuul {@link com.netflix.zuul.context.RequestContext} key for use in load balancer. */ public static final String SERVICE_ID_KEY = "serviceId"; /** * Zuul {@link com.netflix.zuul.context.RequestContext} key for proxying (route ID). */ public static final String PROXY_ID_KEY = "proxy"; /** * {@link ZuulFilter#filterType()} error type. */ public static final String ERROR_TYPE = "error"; /** * {@link ZuulFilter#filterType()} post type. */ public static final String POST_TYPE = "post"; /** * {@link ZuulFilter#filterType()} pre type. */ public static final String PRE_TYPE = "pre"; /** * {@link ZuulFilter#filterType()} route type. */ public static final String ROUTE_TYPE = "route"; /** * Filter Order for SEND_RESPONSE_FILTER_ORDER */ public static final int SEND_RESPONSE_FILTER_ORDER = 1000; /** * Zuul use Sentinel as default context when serviceId is empty. */ public static final String ZUUL_DEFAULT_CONTEXT = "zuul_default_context"; /** * Zuul context key for keeping Sentinel entries. * * @since 1.6.0 */ public static final String ZUUL_CTX_SENTINEL_ENTRIES_KEY = "_sentinel_entries"; private ZuulConstant(){} } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/BlockResponse.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; /** * Fall back response for {@link com.alibaba.csp.sentinel.slots.block.BlockException} * * @author tiger */ public class BlockResponse { /** * HTTP status code. */ private int code; private String message; private String route; public BlockResponse(int code, String message, String route) { this.code = code; this.message = message; this.route = route; } 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 String getRoute() { return route; } public void setRoute(String route) { this.route = route; } @Override public String toString() { return "{" + "\"code\":" + code + ", \"message\":" + "\"" + message + "\"" + ", \"route\":" + "\"" + route + "\"" + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/DefaultBlockFallbackProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Default Fallback provider for sentinel {@link BlockException}, {@literal *} meant for all routes. * * @author tiger */ public class DefaultBlockFallbackProvider implements ZuulBlockFallbackProvider { @Override public String getRoute() { return "*"; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { if (cause instanceof BlockException) { return new BlockResponse(429, "Sentinel block exception", route); } else { return new BlockResponse(500, "System Error", route); } } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/ZuulBlockFallbackManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.util.AssertUtil; /** * This provide fall back class manager. * * @author tiger */ public class ZuulBlockFallbackManager { private static Map fallbackProviderCache = new HashMap<>(); private static ZuulBlockFallbackProvider defaultFallbackProvider = new DefaultBlockFallbackProvider(); /** * Register special provider for different route. */ public static synchronized void registerProvider(ZuulBlockFallbackProvider provider) { AssertUtil.notNull(provider, "fallback provider cannot be null"); String route = provider.getRoute(); if ("*".equals(route) || route == null) { defaultFallbackProvider = provider; } else { fallbackProviderCache.put(route, provider); } } public static ZuulBlockFallbackProvider getFallbackProvider(String route) { ZuulBlockFallbackProvider provider = fallbackProviderCache.get(route); if (provider == null) { provider = defaultFallbackProvider; } return provider; } public synchronized static void clear(){ fallbackProviderCache.clear(); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/ZuulBlockFallbackProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; /** * This interface is compatible for different spring cloud version. * * @author tiger */ public interface ZuulBlockFallbackProvider { /** * The route this fallback will be used for. * @return The route the fallback will be used for. */ String getRoute(); /** * Provides a fallback response based on the cause of the failed execution. * * @param route The route the fallback is for * @param cause cause of the main method failure, may be null * @return the fallback response */ BlockResponse fallbackResponse(String route, Throwable cause); } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/EntryHolder.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import com.alibaba.csp.sentinel.Entry; /** * @author wavesZh */ class EntryHolder { final private Entry entry; final private Object[] params; public EntryHolder(Entry entry, Object[] params) { this.entry = entry; this.params = params; } public Entry getEntry() { return entry; } public Object[] getParams() { return params; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelEntryUtils.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import java.util.Deque; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant; import com.alibaba.csp.sentinel.context.ContextUtil; import com.netflix.zuul.context.RequestContext; /** * @author Eric Zhao * @since 1.6.0 */ final class SentinelEntryUtils { @SuppressWarnings("unchecked") static void tryExitFromCurrentContext() { RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY)) { Deque holders = (Deque) ctx.get(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY); EntryHolder holder; while (!holders.isEmpty()) { holder = holders.pop(); exit(holder); } ctx.remove(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY); } ContextUtil.exit(); } @SuppressWarnings("unchecked") static void tryTraceExceptionThenExitFromCurrentContext(Throwable t) { RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY)) { Deque holders = (Deque) ctx.get(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY); EntryHolder holder; while (!holders.isEmpty()) { holder = holders.pop(); Tracer.traceEntry(t, holder.getEntry()); exit(holder); } ctx.remove(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY); } ContextUtil.exit(); } static void exit(EntryHolder holder) { Entry entry = holder.getEntry(); entry.exit(1, holder.getParams()); } private SentinelEntryUtils() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulErrorFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; /** * This filter track routing exception and exit entry; * * @author tiger * @author Eric Zhao */ public class SentinelZuulErrorFilter extends ZuulFilter { private final int order; public SentinelZuulErrorFilter() { this(-1); } public SentinelZuulErrorFilter(int order) { this.order = order; } @Override public String filterType() { return ZuulConstant.ERROR_TYPE; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return ctx.getThrowable() != null; } @Override public int filterOrder() { return order; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); Throwable throwable = ctx.getThrowable(); if (throwable != null) { if (!BlockException.isBlockException(throwable)) { // Trace exception for each entry and exit entries in order. // The entries can be retrieved from the request context. SentinelEntryUtils.tryTraceExceptionThenExitFromCurrentContext(throwable); RecordLog.info("[SentinelZuulErrorFilter] Trace error cause", throwable.getCause()); } } return null; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulPostFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.exception.ZuulException; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.SEND_RESPONSE_FILTER_ORDER; /** * This filter will mark complete and exit {@link com.alibaba.csp.sentinel.Entry}. * * @author tiger * @author Eric Zhao */ public class SentinelZuulPostFilter extends ZuulFilter { private final int order; public SentinelZuulPostFilter() { this(SEND_RESPONSE_FILTER_ORDER); } public SentinelZuulPostFilter(int order) { this.order = order; } @Override public String filterType() { return ZuulConstant.POST_TYPE; } @Override public int filterOrder() { return order; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { // Exit the entries in order. // The entries can be retrieved from the request context. SentinelEntryUtils.tryExitFromCurrentContext(); return null; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulPreFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.AsyncEntry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.zuul.RequestContextItemParser; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.ZuulGatewayApiMatcherManager; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.matcher.RequestContextApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.zuul.callback.ZuulGatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant; import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse; import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackProvider; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import javax.servlet.http.HttpServletRequest; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; /** * This pre-filter will regard all {@code proxyId} and all customized API as resources. * When a BlockException caught, the filter will try to find a fallback to execute. * * @author tiger * @author Eric Zhao */ public class SentinelZuulPreFilter extends ZuulFilter { private final int order; private final GatewayParamParser paramParser; public SentinelZuulPreFilter() { this(10000); } public SentinelZuulPreFilter(int order) { this(order, new RequestContextItemParser()); } public SentinelZuulPreFilter(int order, RequestItemParser requestItemParser) { AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); this.order = order; this.paramParser = new GatewayParamParser<>(requestItemParser); } @Override public String filterType() { return ZuulConstant.PRE_TYPE; } /** * This run before route filter so we can get more accurate RT time. */ @Override public int filterOrder() { return order; } @Override public boolean shouldFilter() { return true; } private void doSentinelEntry(String resourceName, final int resType, RequestContext requestContext, Deque holders) throws BlockException { Object[] params = paramParser.parseParameterFor(resourceName, requestContext, new Predicate() { @Override public boolean test(GatewayFlowRule r) { return r.getResourceMode() == resType; } }); AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, params); EntryHolder holder = new EntryHolder(entry, params); holders.push(holder); } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); String origin = parseOrigin(ctx.getRequest()); String routeId = (String)ctx.get(ZuulConstant.PROXY_ID_KEY); Deque holders = new ArrayDeque<>(); String fallBackRoute = routeId; try { if (StringUtil.isNotBlank(routeId)) { ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId, origin); doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, ctx, holders); } Set matchingApis = pickMatchingApiDefinitions(ctx); if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) { ContextUtil.enter(ZuulConstant.ZUUL_DEFAULT_CONTEXT, origin); } for (String apiName : matchingApis) { fallBackRoute = apiName; doSentinelEntry(apiName, RESOURCE_MODE_CUSTOM_API_NAME, ctx, holders); } } catch (BlockException ex) { ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager.getFallbackProvider( fallBackRoute); BlockResponse blockResponse = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, ex); // Prevent routing from running ctx.setRouteHost(null); ctx.set(ZuulConstant.SERVICE_ID_KEY, null); // Set fallback response. ctx.setResponseBody(blockResponse.toString()); ctx.setResponseStatusCode(blockResponse.getCode()); // Set Response ContentType ctx.getResponse().setContentType("application/json; charset=utf-8"); } finally { // We don't exit the entry here. We need to exit the entries in post filter to record Rt correctly. // So here the entries will be carried in the request context. if (!holders.isEmpty()) { ctx.put(ZuulConstant.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders); } } return null; } private String parseOrigin(HttpServletRequest request) { return ZuulGatewayCallbackManager.getOriginParser().parseOrigin(request); } private Set pickMatchingApiDefinitions(RequestContext requestContext) { Set apis = new HashSet<>(); for (RequestContextApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) { if (matcher.test(requestContext)) { apis.add(matcher.getApiName()); } } return apis; } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver ================================================ com.alibaba.csp.sentinel.adapter.gateway.zuul.api.ZuulApiDefinitionChangeObserver ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/ZuulBlockFallbackManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Assert; import org.junit.Test; /** * @author tiger */ public class ZuulBlockFallbackManagerTest { private String ROUTE = "/test"; private String DEFAULT_ROUTE = "*"; class MyNullResponseFallBackProvider implements ZuulBlockFallbackProvider { @Override public String getRoute() { return ROUTE; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { return null; } } @Test public void testRegisterProvider() throws Exception { MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); Assert.assertNull(myNullResponseFallBackProvider.fallbackResponse(ROUTE, new FlowException("flow ex"))); } @Test public void clear() { MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); ZuulBlockFallbackManager.clear(); Assert.assertEquals(ZuulBlockFallbackManager.getFallbackProvider(ROUTE).getRoute(), DEFAULT_ROUTE); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/fallback/ZuulBlockFallbackProviderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Assert; import org.junit.Test; /** * @author tiger */ public class ZuulBlockFallbackProviderTest { private String ALL_ROUTE = "*"; @Test public void testGetNullRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(null); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testGetDefaultRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testGetNotInCacheRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider("/not/in"); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testFlowControlFallbackResponse() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new FlowException("flow exception")); Assert.assertEquals(clientHttpResponse.getCode(), 429); } @Test public void testRuntimeExceptionFallbackResponse() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new RuntimeException()); Assert.assertEquals(clientHttpResponse.getCode(), 500); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulErrorFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import com.netflix.zuul.context.RequestContext; import org.junit.Assert; import org.junit.Test; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.ERROR_TYPE; /** * @author tiger */ public class SentinelZuulErrorFilterTest { @Test public void testFilterType() throws Exception { SentinelZuulErrorFilter sentinelZuulErrorFilter = new SentinelZuulErrorFilter(); Assert.assertEquals(sentinelZuulErrorFilter.filterType(), ERROR_TYPE); } @Test public void testShouldFilter() { SentinelZuulErrorFilter sentinelZuulErrorFilter = new SentinelZuulErrorFilter(); RequestContext ctx = RequestContext.getCurrentContext(); ctx.setThrowable(new RuntimeException()); Assert.assertTrue(sentinelZuulErrorFilter.shouldFilter()); } @Test public void testRun() throws Exception { SentinelZuulErrorFilter sentinelZuulErrorFilter = new SentinelZuulErrorFilter(); Object result = sentinelZuulErrorFilter.run(); Assert.assertNull(result); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulPostFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import org.junit.Assert; import org.junit.Test; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.POST_TYPE; /** * @author tiger */ public class SentinelZuulPostFilterTest { @Test public void testFilterType() throws Exception { SentinelZuulPostFilter sentinelZuulPostFilter = new SentinelZuulPostFilter(); Assert.assertEquals(sentinelZuulPostFilter.filterType(), POST_TYPE); } @Test public void testRun() throws Exception { SentinelZuulPostFilter sentinelZuulPostFilter = new SentinelZuulPostFilter(); Object result = sentinelZuulPostFilter.run(); Assert.assertNull(result); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/filters/SentinelZuulPreFilterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.filters; import com.netflix.zuul.context.RequestContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import javax.servlet.http.HttpServletRequest; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.PRE_TYPE; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.SERVICE_ID_KEY; import static org.mockito.Mockito.when; /** * @author tiger */ public class SentinelZuulPreFilterTest { private String SERVICE_ID = "servicea"; private String URI = "/servicea/test"; @Mock private HttpServletRequest httpServletRequest; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(httpServletRequest.getContextPath()).thenReturn(""); when(httpServletRequest.getPathInfo()).thenReturn(URI); RequestContext requestContext = new RequestContext(); requestContext.set(SERVICE_ID_KEY, SERVICE_ID); requestContext.setRequest(httpServletRequest); RequestContext.testSetCurrentContext(requestContext); } @Test public void testFilterType() throws Exception { SentinelZuulPreFilter sentinelZuulPreFilter = new SentinelZuulPreFilter(); Assert.assertEquals(sentinelZuulPreFilter.filterType(), PRE_TYPE); } } ================================================ FILE: sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/route/SentinelZuulRouteTest.java ================================================ package com.alibaba.csp.sentinel.adapter.gateway.zuul.route; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route.PrefixRoutePathMatcher; import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route.RegexRoutePathMatcher; import com.netflix.zuul.context.RequestContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.SERVICE_ID_KEY; /** * @author: jiangzian **/ public class SentinelZuulRouteTest { private final String SERVICE_ID = "servicea"; private final String SERVER_NAME = "www.example.com"; private final String REQUEST_URI = "/servicea/test.jsp"; private final String QUERY_STRING = "param1=value1¶m"; private RequestContext requestContext = new RequestContext(); @Before public void setUp() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setServerName(SERVER_NAME); request.setRequestURI(REQUEST_URI); request.setQueryString(QUERY_STRING); requestContext.set(SERVICE_ID_KEY, SERVICE_ID); requestContext.setRequest(request); RequestContext.testSetCurrentContext(requestContext); } @Test public void testPrefixRoutePathMatche() { PrefixRoutePathMatcher prefixRoutePathMatcher = new PrefixRoutePathMatcher("/servicea/????.jsp"); Assert.assertTrue(prefixRoutePathMatcher.test(requestContext)); prefixRoutePathMatcher = new PrefixRoutePathMatcher("/servicea/????.do"); Assert.assertTrue(!prefixRoutePathMatcher.test(requestContext)); } @Test public void testRegexRoutePathMatcher() { RegexRoutePathMatcher regexRoutePathMatcher = new RegexRoutePathMatcher("/servicea/[a-zA-z]+(\\.jsp)"); Assert.assertTrue(regexRoutePathMatcher.test(requestContext)); regexRoutePathMatcher = new RegexRoutePathMatcher("/serviceb/[a-zA-z]+(\\.jsp)"); Assert.assertTrue(!regexRoutePathMatcher.test(requestContext)); } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/README.md ================================================ # Sentinel Zuul 2.x Adapter This adapter provides **route level** and **customized API level** flow control for Zuul 2.x API Gateway. > *Note*: this adapter only supports Zuul 2.x. ## How to use > You can refer to demo [`sentinel-demo-zuul2-gateway`](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-zuul2-gateway). 1. Add Maven dependency to your `pom.xml`: ```xml com.alibaba.csp sentinel-zuul2-adapter x.y.z ``` 2. Register filters ```java filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500)); filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500)); filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint()); ``` ## How it works As Zuul 2.x is based on Netty, an event-driven asynchronous model, so we use `AsyncEntry`. - `SentinelZuulInboundFilter`: This inbound filter will regard all routes (`routeVIP` in `SessionContext` by default) and all customized API as resources. When a `BlockException` caught, the filter will set endpoint to find a fallback to execute. - `SentinelZuulOutboundFilter`: When the response has no exception caught, the post filter will trace the exception and complete the entries. - `SentinelZuulEndpoint`: When an exception is caught, the filter will find a fallback to execute. ## Integration with Sentinel Dashboard 1. Start [Sentinel Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard). 2. You can configure the rules in Sentinel dashboard or via dynamic rule configuration. > You may need to add `-Dcsp.sentinel.app.type=1` property to mark this application as API gateway. ## Fallbacks You can implement `ZuulBlockFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown. The default fallback provider is `DefaultBlockFallbackProvider`. By default fallback route is proxy ID (or customized API name). Here is an example: ```java // custom provider public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider { private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class); // you can define root as service level @Override public String getRoute() { return "my-route"; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route)); if (cause instanceof BlockException) { return new BlockResponse(429, "Sentinel block exception", route); } else { return new BlockResponse(500, "System Error", route); } } } // register fallback ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider()); ``` Default block response: ```json { "code":429, "message":"Sentinel block exception", "route":"/" } ``` ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/pom.xml ================================================ com.alibaba.csp sentinel-adapter ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-zuul2-adapter jar 1.8 1.8 2.1.5 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-api-gateway-adapter-common com.netflix.zuul zuul-core ${zuul.version} provided org.mockito mockito-core org.springframework spring-core 5.1.18.RELEASE junit junit test org.mockito mockito-core test ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.netflix.zuul.message.http.HttpRequestMessage; /** * @author wavesZh * @since 1.7.2 */ public class HttpRequestMessageItemParser implements RequestItemParser { @Override public String getPath(HttpRequestMessage request) { return request.getInboundRequest().getPath(); } @Override public String getRemoteAddress(HttpRequestMessage request) { return request.getOriginalHost(); } @Override public String getHeader(HttpRequestMessage request, String key) { return String.valueOf(request.getInboundRequest().getHeaders().get(key)); } @Override public String getUrlParam(HttpRequestMessage request, String paramName) { return String.valueOf(request.getInboundRequest().getQueryParams().get(paramName)); } @Override public String getCookieValue(HttpRequestMessage request, String cookieName) { return String.valueOf(request.getInboundRequest().parseCookies().get(cookieName)); } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver; /** * @author Eric Zhao * @since 1.7.2 */ public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver { @Override public void onChange(Set apiDefinitions) { ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher; /** * @author wavesZh * @since 1.7.2 */ public final class ZuulGatewayApiMatcherManager { private static final Map API_MATCHER_MAP = new ConcurrentHashMap<>(); public static Map getApiMatcherMap() { return Collections.unmodifiableMap(API_MATCHER_MAP); } public static HttpRequestMessageApiMatcher getMatcher(final String apiName) { if (apiName == null) { return null; } return API_MATCHER_MAP.get(apiName); } public static Set getApiDefinitionSet() { Set set = new HashSet<>(); for (HttpRequestMessageApiMatcher matcher : API_MATCHER_MAP.values()) { set.add(matcher.getApiDefinition()); } return set; } static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) { if (definitions == null || definitions.isEmpty()) { API_MATCHER_MAP.clear(); return; } for (ApiDefinition definition : definitions) { addApiDefinition(definition); } } static void addApiDefinition(ApiDefinition definition) { API_MATCHER_MAP.put(definition.getApiName(), new HttpRequestMessageApiMatcher(definition)); } private ZuulGatewayApiMatcherManager() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route.ZuulRouteMatchers; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.message.http.HttpRequestMessage; /** * @author wavesZh */ public class HttpRequestMessageApiMatcher extends AbstractApiMatcher { public HttpRequestMessageApiMatcher(ApiDefinition apiDefinition) { super(apiDefinition); } @Override protected void initializeMatchers() { if (apiDefinition.getPredicateItems() != null) { for (ApiPredicateItem item : apiDefinition.getPredicateItems()) { Predicate predicate = fromApiPredicate(item); if (predicate != null) { matchers.add(predicate); } } } } private Predicate fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { if (item instanceof ApiPathPredicateItem) { return fromApiPathPredicate((ApiPathPredicateItem)item); } return null; } private Predicate fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) { String pattern = item.getPattern(); if (StringUtil.isBlank(pattern)) { return null; } switch (item.getMatchStrategy()) { case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX: return ZuulRouteMatchers.regexPath(pattern); case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX: return ZuulRouteMatchers.antPath(pattern); default: return ZuulRouteMatchers.exactPath(pattern); } } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.message.http.HttpRequestMessage; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; /** * @author wavesZh */ public class PrefixRoutePathMatcher implements Predicate { private final String pattern; private final PathMatcher pathMatcher; private final boolean canMatch; public PrefixRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.pathMatcher = new AntPathMatcher(); this.canMatch = pathMatcher.isPattern(pattern); } @Override public boolean test(HttpRequestMessage context) { String path = context.getPath(); if (canMatch) { return pathMatcher.match(pattern, path); } return false; } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; import java.util.regex.Pattern; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.message.http.HttpRequestMessage; /** * @author wavesZh */ public class RegexRoutePathMatcher implements Predicate { private final String pattern; private final Pattern regex; public RegexRoutePathMatcher(String pattern) { AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); this.pattern = pattern; this.regex = Pattern.compile(pattern); } @Override public boolean test(HttpRequestMessage input) { String path = input.getInboundRequest().getPath(); return regex.matcher(path).matches(); } public String getPattern() { return pattern; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; import com.alibaba.csp.sentinel.util.function.Predicate; import com.netflix.zuul.message.http.HttpRequestMessage; /** * @author wavesZh */ public final class ZuulRouteMatchers { public static Predicate all() { return requestContext -> true; } public static Predicate antPath(String pathPattern) { return new PrefixRoutePathMatcher(pathPattern); } public static Predicate exactPath(final String path) { return exchange -> exchange.getPath().equals(path); } public static Predicate regexPath(String pathPattern) { return new RegexRoutePathMatcher(pathPattern); } private ZuulRouteMatchers() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/SentinelZuul2Constants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants; /** * @author wavesZh */ public class SentinelZuul2Constants { /** * The default entrance (context) name when the routeId is empty. */ public static final String ZUUL_DEFAULT_CONTEXT = "zuul2_default_context"; /** * Zuul context key for keeping Sentinel entries. */ public static final String ZUUL_CTX_SENTINEL_ENTRIES_KEY = "_sentinel_entries"; public static final String ZUUL_CTX_SENTINEL_FALLBACK_ROUTE = "_sentinel_fallback_route"; /** * Indicate if request is blocked . */ public static final String ZUUL_CTX_SENTINEL_BLOCKED_FLAG = "_sentinel_blocked_flag"; private SentinelZuul2Constants() {} } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; /** * Fall back response for {@link com.alibaba.csp.sentinel.slots.block.BlockException} * * @author tiger */ public class BlockResponse { /** * HTTP status code. */ private int code; private String message; private String route; public BlockResponse(int code, String message, String route) { this.code = code; this.message = message; this.route = route; } 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 String getRoute() { return route; } public void setRoute(String route) { this.route = route; } @Override public String toString() { return "{" + "\"code\":" + code + ", \"message\":" + "\"" + message + "\"" + ", \"route\":" + "\"" + route + "\"" + '}'; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Default fallback provider for Sentinel {@link BlockException}, {@literal *} meant for all routes. * * @author tiger */ public class DefaultBlockFallbackProvider implements ZuulBlockFallbackProvider { @Override public String getRoute() { return "*"; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { if (cause instanceof BlockException) { return new BlockResponse(429, "SentinelBlockException", route); } else { return new BlockResponse(500, "System Error", route); } } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.util.AssertUtil; /** * This provide fall back class manager. * * @author tiger */ public class ZuulBlockFallbackManager { private static Map fallbackProviderCache = new HashMap<>(); private static ZuulBlockFallbackProvider defaultFallbackProvider = new DefaultBlockFallbackProvider(); /** * Register special provider for different route. */ public static synchronized void registerProvider(ZuulBlockFallbackProvider provider) { AssertUtil.notNull(provider, "fallback provider cannot be null"); String route = provider.getRoute(); if ("*".equals(route) || route == null) { defaultFallbackProvider = provider; } else { fallbackProviderCache.put(route, provider); } } public static ZuulBlockFallbackProvider getFallbackProvider(String route) { ZuulBlockFallbackProvider provider = fallbackProviderCache.get(route); if (provider == null) { provider = defaultFallbackProvider; } return provider; } public synchronized static void clear(){ fallbackProviderCache.clear(); } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; /** * This interface is compatible for different spring cloud version. * * @author tiger */ public interface ZuulBlockFallbackProvider { /** * The route this fallback will be used for. * @return The route the fallback will be used for. */ String getRoute(); /** * Provides a fallback response based on the cause of the failed execution. * * @param route The route the fallback is for * @param cause cause of the main method failure, may be null * @return the fallback response */ BlockResponse fallbackResponse(String route, Throwable cause); } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters; import com.alibaba.csp.sentinel.Entry; /** * @author wavesZh */ public class EntryHolder { final private Entry entry; final private Object[] params; public EntryHolder(Entry entry, Object[] params) { this.entry = entry; this.params = params; } public Entry getEntry() { return entry; } public Object[] getParams() { return params; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.BlockResponse; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackProvider; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.filters.http.HttpSyncEndpoint; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.message.http.HttpResponseMessageImpl; /** * Default Endpoint for handling exception. * * @author wavesZh */ public class SentinelZuulEndpoint extends HttpSyncEndpoint { @Override public HttpResponseMessage apply(HttpRequestMessage request) { SessionContext context = request.getContext(); Throwable throwable = context.getError(); String fallBackRoute = (String) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE); ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager .getFallbackProvider(fallBackRoute); BlockResponse response = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, throwable); HttpResponseMessage resp = new HttpResponseMessageImpl(context, request, response.getCode()); resp.setBodyAsText(response.toString()); return resp; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Function; import com.alibaba.csp.sentinel.AsyncEntry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.HttpRequestMessageItemParser; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulGatewayApiMatcherManager; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.filters.http.HttpInboundFilter; import com.netflix.zuul.message.http.HttpRequestMessage; import rx.Observable; import rx.schedulers.Schedulers; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; /** * The Zuul inbound filter wrapped with Sentinel route and customized API group entries. * * @author wavesZh */ public class SentinelZuulInboundFilter extends HttpInboundFilter { private static final String DEFAULT_BLOCK_ENDPOINT_NAME = SentinelZuulEndpoint.class.getCanonicalName(); private final int order; private final String blockedEndpointName; /** * If the executor is null, flow control action will be performed on I/O thread */ private final Executor executor; /** * If true, the rest of inbound filters will be skipped when the request is blocked. */ private final boolean fastError; private final Function routeExtractor; private final GatewayParamParser paramParser; /** * Constructor of the inbound filter, which extracts the route from the context route VIP attribute by default. * * @param order the order of the filter */ public SentinelZuulInboundFilter(int order) { this(order, m -> m.getContext().getRouteVIP()); } public SentinelZuulInboundFilter(int order, Function routeExtractor) { this(order, null, routeExtractor); } public SentinelZuulInboundFilter(int order, Executor executor, Function routeExtractor) { this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true, routeExtractor); } /** * Constructor of the inbound filter. * * @param order the order of the filter * @param blockedEndpointName the endpoint to go when the request is blocked * @param executor the executor where Sentinel do flow checking. If null, it will be executed in current thread. * @param fastError whether the rest of the filters will be skipped if the request is blocked * @param routeExtractor the route ID extractor */ public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError, Function routeExtractor) { this(order, blockedEndpointName, executor, fastError, routeExtractor, new HttpRequestMessageItemParser()); } public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError, Function routeExtractor, RequestItemParser requestItemParser) { AssertUtil.notEmpty(blockedEndpointName, "blockedEndpointName cannot be empty"); AssertUtil.notNull(routeExtractor, "routeExtractor cannot be null"); AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null"); this.order = order; this.blockedEndpointName = blockedEndpointName; this.executor = executor; this.fastError = fastError; this.routeExtractor = routeExtractor; this.paramParser = new GatewayParamParser<>(requestItemParser); } @Override public int filterOrder() { return order; } @Override public Observable applyAsync(HttpRequestMessage request) { if (executor != null) { return Observable.just(request).subscribeOn(Schedulers.from(executor)).flatMap(this::apply); } else { return Observable.just(request).flatMap(this::apply); } } private Observable apply(HttpRequestMessage request) { SessionContext context = request.getContext(); Deque holders = new ArrayDeque<>(); String routeId = routeExtractor.apply(request); String fallBackRoute = routeId; try { if (StringUtil.isNotBlank(routeId)) { ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId); doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders); } Set matchingApis = pickMatchingApiDefinitions(request); if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) { ContextUtil.enter(SentinelZuul2Constants.ZUUL_DEFAULT_CONTEXT); } for (String apiName : matchingApis) { fallBackRoute = apiName; doSentinelEntry(apiName, RESOURCE_MODE_CUSTOM_API_NAME, request, holders); } return Observable.just(request); } catch (BlockException t) { context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE); context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute); if (fastError) { context.setShouldSendErrorResponse(true); context.setErrorEndpoint(blockedEndpointName); } else { context.setEndpoint(blockedEndpointName); } return Observable.error(t); } finally { if (!holders.isEmpty()) { context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders); } // clear context to avoid another request use incorrect context ContextUtil.exit(); } } private void doSentinelEntry(String resourceName, final int resType, HttpRequestMessage input, Deque holders) throws BlockException { Object[] params = paramParser.parseParameterFor(resourceName, input, r -> r.getResourceMode() == resType); AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, params); holders.push(new EntryHolder(entry, params)); } private Set pickMatchingApiDefinitions(HttpRequestMessage message) { Set apis = new HashSet<>(); for (HttpRequestMessageApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) { if (matcher.test(message)) { apis.add(matcher.getApiName()); } } return apis; } @Override public boolean shouldFilter(HttpRequestMessage msg) { return true; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound; import java.util.Deque; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.filters.http.HttpOutboundFilter; import com.netflix.zuul.message.http.HttpResponseMessage; import rx.Observable; /** * The Zuul outbound filter which will complete the Sentinel entries and * trace the exception that happened in previous filters. * * @author wavesZh */ public class SentinelZuulOutboundFilter extends HttpOutboundFilter { private final int order; public SentinelZuulOutboundFilter(int order) { this.order = order; } @Override public int filterOrder() { return order; } @Override public Observable applyAsync(HttpResponseMessage input) { return Observable.just(apply(input)); } public HttpResponseMessage apply(HttpResponseMessage response) { SessionContext context = response.getContext(); if (context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) { return response; } boolean previousBlocked = context.getFilterErrors().stream() .anyMatch(e -> BlockException.isBlockException(e.getException())); Deque holders = (Deque) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY); while (!holders.isEmpty()) { EntryHolder holder = holders.pop(); if (!previousBlocked) { Tracer.traceEntry(context.getError(), holder.getEntry()); holder.getEntry().exit(1, holder.getParams()); } } return response; } @Override public boolean shouldFilter(HttpResponseMessage msg) { return true; } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver ================================================ com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulApiDefinitionChangeObserver ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Assert; import org.junit.Test; /** * @author tiger */ public class ZuulBlockFallbackManagerTest { private String ROUTE = "/test"; private String DEFAULT_ROUTE = "*"; class MyNullResponseFallBackProvider implements ZuulBlockFallbackProvider { @Override public String getRoute() { return ROUTE; } @Override public BlockResponse fallbackResponse(String route, Throwable cause) { return null; } } @Test public void testRegisterProvider() throws Exception { MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); Assert.assertNull(myNullResponseFallBackProvider.fallbackResponse(ROUTE, new FlowException("flow ex"))); } @Test public void clear() { MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); ZuulBlockFallbackManager.clear(); Assert.assertEquals(ZuulBlockFallbackManager.getFallbackProvider(ROUTE).getRoute(), DEFAULT_ROUTE); } } ================================================ FILE: sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Assert; import org.junit.Test; /** * @author tiger */ public class ZuulBlockFallbackProviderTest { private String ALL_ROUTE = "*"; @Test public void testGetNullRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(null); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testGetDefaultRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testGetNotInCacheRoute() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider("/not/in"); Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); } @Test public void testFlowControlFallbackResponse() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new FlowException("flow exception")); Assert.assertEquals(clientHttpResponse.getCode(), 429); } @Test public void testRuntimeExceptionFallbackResponse() throws Exception { ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new RuntimeException()); Assert.assertEquals(clientHttpResponse.getCode(), 500); } } ================================================ FILE: sentinel-benchmark/pom.xml ================================================ com.alibaba.csp sentinel-parent ${revision} ../pom.xml 4.0.0 sentinel-benchmark jar Sentinel JMH benchmark 3.0 com.alibaba.csp sentinel-core org.openjdk.jmh jmh-core ${jmh.version} org.openjdk.jmh jmh-generator-annprocess ${jmh.version} provided UTF-8 1.21 benchmarks org.apache.maven.plugins maven-pmd-plugin ${maven.pmd.version} true org.apache.maven.plugins maven-deploy-plugin ${maven.deploy.version} true org.apache.maven.plugins maven-gpg-plugin true org.apache.maven.plugins maven-shade-plugin 3.1.1 package shade ${uberjar.name} org.openjdk.jmh.Main *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: sentinel-benchmark/src/main/java/com/alibaba/csp/sentinel/benchmark/SentinelEntryBenchmark.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.benchmark; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; /** * Benchmark for Sentinel entries. * * @author Eric Zhao */ @Warmup(iterations = 10) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class SentinelEntryBenchmark { @Param({"25", "50", "100", "200", "500", "1000"}) private int length; private List numbers; @Setup public void prepare() { numbers = new ArrayList<>(); for (int i = 0; i < length; i++) { numbers.add(ThreadLocalRandom.current().nextInt()); } } private void doSomething() { Collections.shuffle(numbers); Collections.sort(numbers); } private void doSomethingWithEntry() { Entry e0 = null; try { e0 = SphU.entry("benchmark"); doSomething(); } catch (BlockException e) { } finally { if (e0 != null) { e0.exit(); } } } @Benchmark @Threads(1) public void testSingleThreadDirectly() { doSomething(); } @Benchmark @Threads(1) public void testSingleThreadSingleEntry() { doSomethingWithEntry(); } @Benchmark @Threads(2) public void test2ThreadsSingleEntry() { doSomethingWithEntry(); } @Benchmark @Threads(3) public void test3ThreadsSingleEntry() { doSomethingWithEntry(); } @Benchmark @Threads(4) public void test4ThreadsDirectly() { doSomething(); } @Benchmark @Threads(4) public void test4ThreadsSingleEntry() { doSomethingWithEntry(); } @Benchmark @Threads(8) public void test8ThreadsDirectly() { doSomething(); } @Benchmark @Threads(8) public void test8ThreadsSingleEntry() { doSomethingWithEntry(); } @Benchmark @Threads(16) public void test16ThreadsDirectly() { doSomething(); } @Benchmark @Threads(16) public void test16ThreadsSingleEntry() { doSomethingWithEntry(); } } ================================================ FILE: sentinel-cluster/README.md ================================================ # Sentinel Cluster Flow Control This is the default implementation of Sentinel cluster flow control. - `sentinel-cluster-common-default`: common module for cluster transport and functions - `sentinel-cluster-client-default`: default cluster client module using Netty as underlying transport library - `sentinel-cluster-server-default`: default cluster server module ================================================ FILE: sentinel-cluster/pom.xml ================================================ com.alibaba.csp sentinel-parent ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} pom sentinel-cluster The parent module of Sentinel cluster server 4.1.48.Final sentinel-cluster-client-default sentinel-cluster-server-default sentinel-cluster-common-default sentinel-cluster-server-envoy-rls io.netty netty-handler ${netty.version} io.netty netty-all ${netty.version} ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/README.md ================================================ # Sentinel Cluster Client (Default) ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/pom.xml ================================================ com.alibaba.csp sentinel-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-cluster-client-default jar com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-common provided com.alibaba.csp sentinel-cluster-common-default io.netty netty-handler com.alibaba.csp sentinel-datasource-nacos test com.alibaba.csp sentinel-parameter-flow-control test junit junit test org.mockito mockito-core test org.assertj assertj-core test ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClientConstants { public static final int TYPE_PING = 0; public static final int TYPE_FLOW = 1; public static final int TYPE_PARAM_FLOW = 2; public static final int CLIENT_STATUS_OFF = 0; public static final int CLIENT_STATUS_PENDING = 1; public static final int CLIENT_STATUS_STARTED = 2; private ClientConstants() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages; import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.cluster.client.config.ServerChangeObserver; import com.alibaba.csp.sentinel.cluster.log.ClusterClientStatLogUtil; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; /** * Default implementation of {@link ClusterTokenClient}. * * @author Eric Zhao * @since 1.4.0 */ public class DefaultClusterTokenClient implements ClusterTokenClient { private ClusterTransportClient transportClient; private TokenServerDescriptor serverDescriptor; private final AtomicBoolean shouldStart = new AtomicBoolean(false); public DefaultClusterTokenClient() { ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() { @Override public void onRemoteServerChange(ClusterClientAssignConfig assignConfig) { changeServer(assignConfig); } }); initNewConnection(); } private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientAssignConfig config) { if (descriptor == null || config == null) { return false; } return descriptor.getHost().equals(config.getServerHost()) && descriptor.getPort() == config.getServerPort(); } private void initNewConnection() { if (transportClient != null) { return; } String host = ClusterClientConfigManager.getServerHost(); int port = ClusterClientConfigManager.getServerPort(); if (StringUtil.isBlank(host) || port <= 0) { return; } try { this.transportClient = new NettyTransportClient(host, port); this.serverDescriptor = new TokenServerDescriptor(host, port); RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor); } catch (Exception ex) { RecordLog.warn("[DefaultClusterTokenClient] Failed to initialize new token client", ex); } } private void changeServer(/*@Valid*/ ClusterClientAssignConfig config) { if (serverEqual(serverDescriptor, config)) { return; } try { if (transportClient != null) { transportClient.stop(); } // Replace with new, even if the new client is not ready. this.transportClient = new NettyTransportClient(config.getServerHost(), config.getServerPort()); this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort()); startClientIfScheduled(); RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor); } catch (Exception ex) { RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex); } } private void startClientIfScheduled() throws Exception { if (shouldStart.get()) { if (transportClient != null) { transportClient.start(); } else { RecordLog.warn("[DefaultClusterTokenClient] Cannot start transport client: client not created"); } } } private void stopClientIfStarted() throws Exception { if (shouldStart.compareAndSet(true, false)) { if (transportClient != null) { transportClient.stop(); } } } @Override public void start() throws Exception { if (shouldStart.compareAndSet(false, true)) { startClientIfScheduled(); } } @Override public void stop() throws Exception { stopClientIfStarted(); } @Override public int getState() { if (transportClient == null) { return ClientConstants.CLIENT_STATUS_OFF; } return transportClient.isReady() ? ClientConstants.CLIENT_STATUS_STARTED : ClientConstants.CLIENT_STATUS_OFF; } @Override public TokenServerDescriptor currentServer() { return serverDescriptor; } @Override public TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) { if (notValidRequest(flowId, acquireCount)) { return badRequest(); } FlowRequestData data = new FlowRequestData().setCount(acquireCount) .setFlowId(flowId).setPriority(prioritized); ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data); try { TokenResult result = sendTokenRequest(request); logForResult(result); return result; } catch (Exception ex) { ClusterClientStatLogUtil.log(ex.getMessage()); return new TokenResult(TokenResultStatus.FAIL); } } @Override public TokenResult requestParamToken(Long flowId, int acquireCount, Collection params) { if (notValidRequest(flowId, acquireCount) || params == null || params.isEmpty()) { return badRequest(); } ParamFlowRequestData data = new ParamFlowRequestData().setCount(acquireCount) .setFlowId(flowId).setParams(params); ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_PARAM_FLOW, data); try { TokenResult result = sendTokenRequest(request); logForResult(result); return result; } catch (Exception ex) { ClusterClientStatLogUtil.log(ex.getMessage()); return new TokenResult(TokenResultStatus.FAIL); } } @Override public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) { return null; } @Override public void releaseConcurrentToken(Long tokenId) { } private void logForResult(TokenResult result) { switch (result.getStatus()) { case TokenResultStatus.NO_RULE_EXISTS: ClusterClientStatLogUtil.log(ClusterErrorMessages.NO_RULES_IN_SERVER); break; case TokenResultStatus.TOO_MANY_REQUEST: ClusterClientStatLogUtil.log(ClusterErrorMessages.TOO_MANY_REQUESTS); break; default: } } private TokenResult sendTokenRequest(ClusterRequest request) throws Exception { if (transportClient == null) { RecordLog.warn( "[DefaultClusterTokenClient] Client not created, please check your config for cluster client"); return clientFail(); } ClusterResponse response = transportClient.sendRequest(request); TokenResult result = new TokenResult(response.getStatus()); if (response.getData() != null) { FlowTokenResponseData responseData = (FlowTokenResponseData)response.getData(); result.setRemaining(responseData.getRemainingCount()) .setWaitInMs(responseData.getWaitInMs()); } return result; } private boolean notValidRequest(Long id, int count) { return id == null || id <= 0 || count <= 0; } private TokenResult badRequest() { return new TokenResult(TokenResultStatus.BAD_REQUEST); } private TokenResult clientFail() { return new TokenResult(TokenResultStatus.FAIL); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client; import java.util.AbstractMap.SimpleEntry; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages; import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder; import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler; import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder; import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.Request; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.util.concurrent.GenericFutureListener; /** * Netty transport client implementation for Sentinel cluster transport. * * @author Eric Zhao * @since 1.4.0 */ public class NettyTransportClient implements ClusterTransportClient { @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-cluster-transport-client-scheduler", true)); public static final int RECONNECT_DELAY_MS = 2000; private final String host; private final int port; private Channel channel; private NioEventLoopGroup eventLoopGroup; private TokenClientHandler clientHandler; private final AtomicInteger idGenerator = new AtomicInteger(0); private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF); private final AtomicInteger failConnectedTime = new AtomicInteger(0); private final AtomicBoolean shouldRetry = new AtomicBoolean(true); public NettyTransportClient(String host, int port) { AssertUtil.assertNotBlank(host, "remote host cannot be blank"); AssertUtil.isTrue(port > 0, "port should be positive"); this.host = host; this.port = port; } private Bootstrap initClientBootstrap() { Bootstrap b = new Bootstrap(); eventLoopGroup = new NioEventLoopGroup(); b.group(eventLoopGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, ClusterClientConfigManager.getConnectTimeout()) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { clientHandler = new TokenClientHandler(currentState, disconnectCallback); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); pipeline.addLast(new NettyResponseDecoder()); pipeline.addLast(new LengthFieldPrepender(2)); pipeline.addLast(new NettyRequestEncoder()); pipeline.addLast(clientHandler); } }); return b; } private void connect(Bootstrap b) { if (currentState.compareAndSet(ClientConstants.CLIENT_STATUS_OFF, ClientConstants.CLIENT_STATUS_PENDING)) { b.connect(host, port) .addListener(new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.cause() != null) { RecordLog.warn( String.format("[NettyTransportClient] Could not connect to <%s:%d> after %d times", host, port, failConnectedTime.get()), future.cause()); failConnectedTime.incrementAndGet(); channel = null; } else { failConnectedTime.set(0); channel = future.channel(); RecordLog.info("[NettyTransportClient] Successfully connect to server <{}:{}>", host, port); } } }); } } private Runnable disconnectCallback = new Runnable() { @Override public void run() { if (!shouldRetry.get()) { return; } SCHEDULER.schedule(new Runnable() { @Override public void run() { if (shouldRetry.get()) { RecordLog.info("[NettyTransportClient] Reconnecting to server <{}:{}>", host, port); try { startInternal(); } catch (Exception e) { RecordLog.warn("[NettyTransportClient] Failed to reconnect to server", e); } } } }, RECONNECT_DELAY_MS * (failConnectedTime.get() + 1), TimeUnit.MILLISECONDS); cleanUp(); } }; @Override public void start() throws Exception { shouldRetry.set(true); startInternal(); } private void startInternal() { connect(initClientBootstrap()); } private void cleanUp() { if (channel != null) { channel.close(); channel = null; } if (eventLoopGroup != null) { eventLoopGroup.shutdownGracefully(); } } @Override public void stop() throws Exception { // Stop retrying for connection. shouldRetry.set(false); while (currentState.get() == ClientConstants.CLIENT_STATUS_PENDING) { try { Thread.sleep(200); } catch (Exception ex) { // Ignore. } } cleanUp(); failConnectedTime.set(0); RecordLog.info("[NettyTransportClient] Cluster transport client stopped"); } private boolean validRequest(Request request) { return request != null && request.getType() >= 0; } @Override public boolean isReady() { return channel != null && clientHandler != null && clientHandler.hasStarted(); } @Override public ClusterResponse sendRequest(ClusterRequest request) throws Exception { if (!isReady()) { throw new SentinelClusterException(ClusterErrorMessages.CLIENT_NOT_READY); } if (!validRequest(request)) { throw new SentinelClusterException(ClusterErrorMessages.BAD_REQUEST); } int xid = getCurrentId(); try { request.setId(xid); channel.writeAndFlush(request); ChannelPromise promise = channel.newPromise(); TokenClientPromiseHolder.putPromise(xid, promise); if (!promise.await(ClusterClientConfigManager.getRequestTimeout())) { throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT); } SimpleEntry entry = TokenClientPromiseHolder.getEntry(xid); if (entry == null || entry.getValue() == null) { // Should not go through here. throw new SentinelClusterException(ClusterErrorMessages.UNEXPECTED_STATUS); } return entry.getValue(); } finally { TokenClientPromiseHolder.remove(xid); } } private int getCurrentId() { int pre, next; do { pre = idGenerator.get(); next = pre >= MAX_ID ? MIN_ID : pre + 1; } while (!idGenerator.compareAndSet(pre, next)); return next; } /*public CompletableFuture sendRequestAsync(ClusterRequest request) { // Uncomment this when min target JDK is 1.8. if (!validRequest(request)) { return CompletableFuture.failedFuture(new IllegalArgumentException("Bad request")); } int xid = getCurrentId(); request.setId(xid); CompletableFuture future = new CompletableFuture<>(); channel.writeAndFlush(request) .addListener(f -> { if (f.isSuccess()) { future.complete(someResult); } else if (f.cause() != null) { future.completeExceptionally(f.cause()); } else { future.cancel(false); } }); return future; }*/ private static final int MIN_ID = 1; private static final int MAX_ID = 999_999_999; } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec; import com.alibaba.csp.sentinel.spi.SpiLoader; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClientEntityCodecProvider { private static RequestEntityWriter requestEntityWriter = null; private static ResponseEntityDecoder responseEntityDecoder = null; static { resolveInstance(); } private static void resolveInstance() { RequestEntityWriter writer = SpiLoader.of(RequestEntityWriter.class).loadFirstInstance(); if (writer == null) { RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed"); } else { requestEntityWriter = writer; RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: {}", requestEntityWriter.getClass().getCanonicalName()); } ResponseEntityDecoder decoder = SpiLoader.of(ResponseEntityDecoder.class).loadFirstInstance(); if (decoder == null) { RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed"); } else { responseEntityDecoder = decoder; RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: {}", responseEntityDecoder.getClass().getCanonicalName()); } } public static RequestEntityWriter getRequestEntityWriter() { return requestEntityWriter; } public static ResponseEntityDecoder getResponseEntityDecoder() { return responseEntityDecoder; } private ClientEntityCodecProvider() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec; import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.Request; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class DefaultRequestEntityWriter implements RequestEntityWriter { @Override public void writeTo(ClusterRequest request, ByteBuf target) { int type = request.getType(); EntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type); if (requestDataWriter == null) { RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{}>," + " dropping the request", type); return; } // Write head part of request. writeHead(request, target); // Write data part. requestDataWriter.writeTo(request.getData(), target); } private void writeHead(Request request, ByteBuf out) { out.writeInt(request.getId()); out.writeByte(request.getType()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec; import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; /** *

Default entity decoder for any {@link ClusterResponse} entity.

* *

Decode format:

*
 * +--------+---------+-----------+---------+
 * | xid(4) | type(1) | status(1) | data... |
 * +--------+---------+-----------+---------+
 * 
* * @author Eric Zhao * @since 1.4.0 */ public class DefaultResponseEntityDecoder implements ResponseEntityDecoder { @Override public ClusterResponse decode(ByteBuf source) { if (source.readableBytes() >= 6) { int xid = source.readInt(); int type = source.readByte(); int status = source.readByte(); EntityDecoder decoder = ResponseDataDecodeRegistry.getDecoder(type); if (decoder == null) { RecordLog.warn("Unknown type of response data decoder: {}", type); return null; } Object data; if (source.readableBytes() == 0) { data = null; } else { data = decoder.decode(source); } return new ClusterResponse<>(xid, type, status, data); } return null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import io.netty.buffer.ByteBuf; /** * +-------------------+--------------+----------------+---------------+------------------+ * | RequestID(8 byte) | Type(1 byte) | FlowID(8 byte) | Count(4 byte) | PriorityFlag (1) | * +-------------------+--------------+----------------+---------------+------------------+ * * @author Eric Zhao * @since 1.4.0 */ public class FlowRequestDataWriter implements EntityWriter { @Override public void writeTo(FlowRequestData entity, ByteBuf target) { target.writeLong(entity.getFlowId()); target.writeInt(entity.getCount()); target.writeBoolean(entity.isPriority()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class FlowResponseDataDecoder implements EntityDecoder { @Override public FlowTokenResponseData decode(ByteBuf source) { FlowTokenResponseData data = new FlowTokenResponseData(); if (source.readableBytes() == 8) { data.setRemainingCount(source.readInt()); data.setWaitInMs(source.readInt()); } return data; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author jialiang.linjl * @author Eric Zhao * @since 1.4.0 */ public class ParamFlowRequestDataWriter implements EntityWriter { private final int maxParamByteSize; public ParamFlowRequestDataWriter() { this(DEFAULT_PARAM_MAX_SIZE); } public ParamFlowRequestDataWriter(int maxParamByteSize) { AssertUtil.isTrue(maxParamByteSize > 0, "maxParamByteSize should be positive"); this.maxParamByteSize = maxParamByteSize; } @Override public void writeTo(ParamFlowRequestData entity, ByteBuf target) { target.writeLong(entity.getFlowId()); target.writeInt(entity.getCount()); Collection params = entity.getParams(); params = resolveValidParams(params); target.writeInt(params.size()); // Serialize parameters with type flag. for (Object param : params) { encodeValue(param, target); } } /** * Get valid parameters in provided parameter list * * @param params * @return */ public List resolveValidParams(Collection params) { List validParams = new ArrayList<>(); int size = 0; for (Object param : params) { int s = calculateParamTransportSize(param); if (s <= 0) { RecordLog.warn("[ParamFlowRequestDataWriter] WARN: Non-primitive type detected in params of " + "cluster parameter flow control, which is not supported: " + param); continue; } if (size + s > maxParamByteSize) { RecordLog.warn("[ParamFlowRequestDataWriter] WARN: params size is too big." + " the configure value is : " + maxParamByteSize + ", the params size is: " + params.size()); break; } size += s; validParams.add(param); } return validParams; } private void encodeValue(Object param, ByteBuf target) { // Handle primitive type. if (param instanceof Integer || int.class.isInstance(param)) { target.writeByte(ClusterConstants.PARAM_TYPE_INTEGER); target.writeInt((Integer) param); } else if (param instanceof String) { encodeString((String) param, target); } else if (boolean.class.isInstance(param) || param instanceof Boolean) { target.writeByte(ClusterConstants.PARAM_TYPE_BOOLEAN); target.writeBoolean((Boolean) param); } else if (long.class.isInstance(param) || param instanceof Long) { target.writeByte(ClusterConstants.PARAM_TYPE_LONG); target.writeLong((Long) param); } else if (double.class.isInstance(param) || param instanceof Double) { target.writeByte(ClusterConstants.PARAM_TYPE_DOUBLE); target.writeDouble((Double) param); } else if (float.class.isInstance(param) || param instanceof Float) { target.writeByte(ClusterConstants.PARAM_TYPE_FLOAT); target.writeFloat((Float) param); } else if (byte.class.isInstance(param) || param instanceof Byte) { target.writeByte(ClusterConstants.PARAM_TYPE_BYTE); target.writeByte((Byte) param); } else if (short.class.isInstance(param) || param instanceof Short) { target.writeByte(ClusterConstants.PARAM_TYPE_SHORT); target.writeShort((Short) param); } else { // Unexpected type, drop. } } private void encodeString(String param, ByteBuf target) { target.writeByte(ClusterConstants.PARAM_TYPE_STRING); byte[] tmpChars = param.getBytes(); target.writeInt(tmpChars.length); target.writeBytes(tmpChars); } int calculateParamTransportSize(Object value) { if (value == null) { return 0; } // Layout for primitives: |type flag(1)|value| // size = original size + type flag (1) if (value instanceof Integer || int.class.isInstance(value)) { return 5; } else if (value instanceof String) { // Layout for string: |type flag(1)|length(4)|string content| String tmpValue = (String) value; byte[] tmpChars = tmpValue.getBytes(); return 1 + 4 + tmpChars.length; } else if (boolean.class.isInstance(value) || value instanceof Boolean) { return 2; } else if (long.class.isInstance(value) || value instanceof Long) { return 9; } else if (double.class.isInstance(value) || value instanceof Double) { return 9; } else if (float.class.isInstance(value) || value instanceof Float) { return 5; } else if (byte.class.isInstance(value) || value instanceof Byte) { return 2; } else if (short.class.isInstance(value) || value instanceof Short) { return 3; } else { // Ignore unexpected type. return 0; } } private static final int DEFAULT_PARAM_MAX_SIZE = 1024; } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingRequestDataWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class PingRequestDataWriter implements EntityWriter { @Override public void writeTo(String entity, ByteBuf target) { if (StringUtil.isBlank(entity) || target == null) { return; } byte[] bytes = entity.getBytes(); target.writeInt(bytes.length); target.writeBytes(bytes); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class PingResponseDataDecoder implements EntityDecoder { @Override public Integer decode(ByteBuf source) { int size = source.readableBytes(); if (size == 1) { // Compatible with old version (< 1.7.0). return (int) source.readByte(); } if (size >= 4) { return source.readInt(); } return -1; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.netty; import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.Request; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * @author Eric Zhao * @since 1.4.0 */ public class NettyRequestEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception { RequestEntityWriter requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter(); if (requestEntityWriter == null) { RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request"); return; } requestEntityWriter.writeTo(request, out); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.netty; import java.util.List; import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider; import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.Response; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; /** * @author Eric Zhao * @since 1.4.0 */ public class NettyResponseDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { ResponseEntityDecoder responseDecoder = ClientEntityCodecProvider.getResponseEntityDecoder(); if (responseDecoder == null) { RecordLog.warn("[NettyResponseDecoder] Cannot resolve the global response entity decoder, " + "dropping the response"); return; } // TODO: handle decode error here. Response response = responseDecoder.decode(in); if (response != null) { out.add(response); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.registry; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public final class RequestDataWriterRegistry { private static final Map> WRITER_MAP = new HashMap<>(); public static boolean addWriter(int type, EntityWriter writer) { if (WRITER_MAP.containsKey(type)) { return false; } WRITER_MAP.put(type, (EntityWriter)writer); return true; } public static EntityWriter getWriter(int type) { return WRITER_MAP.get(type); } public static boolean remove(int type) { return WRITER_MAP.remove(type) != null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.registry; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public final class ResponseDataDecodeRegistry { private static final Map> DECODER_MAP = new HashMap<>(); public static boolean addDecoder(int type, EntityDecoder decoder) { if (DECODER_MAP.containsKey(type)) { return false; } DECODER_MAP.put(type, decoder); return true; } public static EntityDecoder getDecoder(int type) { return (EntityDecoder)DECODER_MAP.get(type); } public static boolean removeDecoder(int type) { return DECODER_MAP.remove(type) != null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientAssignConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.config; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterClientAssignConfig { private String serverHost; private Integer serverPort; public ClusterClientAssignConfig() {} public ClusterClientAssignConfig(String serverHost, Integer serverPort) { this.serverHost = serverHost; this.serverPort = serverPort; } public String getServerHost() { return serverHost; } public ClusterClientAssignConfig setServerHost(String serverHost) { this.serverHost = serverHost; return this; } public Integer getServerPort() { return serverPort; } public ClusterClientAssignConfig setServerPort(Integer serverPort) { this.serverPort = serverPort; return this; } @Override public String toString() { return "ClusterClientAssignConfig{" + "serverHost='" + serverHost + '\'' + ", serverPort=" + serverPort + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.config; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterClientConfig { private Integer requestTimeout; public Integer getRequestTimeout() { return requestTimeout; } public ClusterClientConfig setRequestTimeout(Integer requestTimeout) { this.requestTimeout = requestTimeout; return this; } @Override public String toString() { return "ClusterClientConfig{" + "requestTimeout=" + requestTimeout + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.config; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterClientConfigManager { /** * Client config properties. */ private static volatile String serverHost = null; private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT; private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT; private static volatile int connectTimeout = ClusterConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; private static final PropertyListener CONFIG_PROPERTY_LISTENER = new ClientConfigPropertyListener(); private static final PropertyListener ASSIGN_PROPERTY_LISTENER = new ClientAssignPropertyListener(); private static SentinelProperty clientConfigProperty = new DynamicSentinelProperty<>(); private static SentinelProperty clientAssignProperty = new DynamicSentinelProperty<>(); private static final List SERVER_CHANGE_OBSERVERS = new ArrayList<>(); static { bindPropertyListener(); } private static void bindPropertyListener() { removePropertyListener(); clientAssignProperty.addListener(ASSIGN_PROPERTY_LISTENER); clientConfigProperty.addListener(CONFIG_PROPERTY_LISTENER); } private static void removePropertyListener() { clientAssignProperty.removeListener(ASSIGN_PROPERTY_LISTENER); clientConfigProperty.removeListener(CONFIG_PROPERTY_LISTENER); } public static void registerServerAssignProperty(SentinelProperty property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (ASSIGN_PROPERTY_LISTENER) { RecordLog.info("[ClusterClientConfigManager] Registering new server assignment property to cluster " + "client config manager"); clientAssignProperty.removeListener(ASSIGN_PROPERTY_LISTENER); property.addListener(ASSIGN_PROPERTY_LISTENER); clientAssignProperty = property; } } public static void registerClientConfigProperty(SentinelProperty property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (CONFIG_PROPERTY_LISTENER) { RecordLog.info("[ClusterClientConfigManager] Registering new global client config property to " + "cluster client config manager"); clientConfigProperty.removeListener(CONFIG_PROPERTY_LISTENER); property.addListener(CONFIG_PROPERTY_LISTENER); clientConfigProperty = property; } } public static void addServerChangeObserver(ServerChangeObserver observer) { AssertUtil.notNull(observer, "observer cannot be null"); SERVER_CHANGE_OBSERVERS.add(observer); } /** * Apply new {@link ClusterClientConfig}, while the former config will be replaced. * * @param config new config to apply */ public static void applyNewConfig(ClusterClientConfig config) { clientConfigProperty.updateValue(config); } public static void applyNewAssignConfig(ClusterClientAssignConfig clusterClientAssignConfig) { clientAssignProperty.updateValue(clusterClientAssignConfig); } private static class ClientAssignPropertyListener implements PropertyListener { @Override public void configLoad(ClusterClientAssignConfig config) { if (config == null) { RecordLog.warn("[ClusterClientConfigManager] Empty initial client assignment config"); return; } applyConfig(config); } @Override public void configUpdate(ClusterClientAssignConfig config) { applyConfig(config); } private synchronized void applyConfig(ClusterClientAssignConfig config) { if (!isValidAssignConfig(config)) { RecordLog.warn( "[ClusterClientConfigManager] Invalid cluster client assign config, ignoring: " + config); return; } if (serverPort == config.getServerPort() && config.getServerHost().equals(serverHost)) { return; } RecordLog.info("[ClusterClientConfigManager] Assign to new target token server: {}", config); updateServerAssignment(config); } } private static class ClientConfigPropertyListener implements PropertyListener { @Override public void configLoad(ClusterClientConfig config) { if (config == null) { RecordLog.warn("[ClusterClientConfigManager] Empty initial client config"); return; } applyConfig(config); } @Override public void configUpdate(ClusterClientConfig config) { applyConfig(config); } private synchronized void applyConfig(ClusterClientConfig config) { if (!isValidClientConfig(config)) { RecordLog.warn( "[ClusterClientConfigManager] Invalid cluster client config, ignoring: {}", config); return; } RecordLog.info("[ClusterClientConfigManager] Updating to new client config: {}", config); updateClientConfigChange(config); } } private static void updateClientConfigChange(ClusterClientConfig config) { if (config.getRequestTimeout() != requestTimeout) { requestTimeout = config.getRequestTimeout(); } } private static void updateServerAssignment(/*@Valid*/ ClusterClientAssignConfig config) { String host = config.getServerHost(); int port = config.getServerPort(); for (ServerChangeObserver observer : SERVER_CHANGE_OBSERVERS) { observer.onRemoteServerChange(config); } serverHost = host; serverPort = port; } public static boolean isValidAssignConfig(ClusterClientAssignConfig config) { return config != null && StringUtil.isNotBlank(config.getServerHost()) && config.getServerPort() > 0 && config.getServerPort() <= 65535; } public static boolean isValidClientConfig(ClusterClientConfig config) { return config != null && config.getRequestTimeout() > 0; } public static String getServerHost() { return serverHost; } public static int getServerPort() { return serverPort; } public static int getRequestTimeout() { return requestTimeout; } public static int getConnectTimeout() { return connectTimeout; } private ClusterClientConfigManager() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientStartUpConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.config; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; /** *

* this class dedicated to reading startup configurations of cluster client *

* * @author lianglin * @since 1.7.0 */ public class ClusterClientStartUpConfig { private static final String MAX_PARAM_BYTE_SIZE = "csp.sentinel.cluster.max.param.byte.size"; /** * Get the max bytes params can be serialized * * @return the max bytes, may be null */ public static Integer getMaxParamByteSize() { String maxParamByteSize = SentinelConfig.getConfig(MAX_PARAM_BYTE_SIZE); try { return maxParamByteSize == null ? null : Integer.valueOf(maxParamByteSize); } catch (Exception ex) { RecordLog.warn("[ClusterClientStartUpConfig] Failed to parse maxParamByteSize: " + maxParamByteSize); return null; } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.config; /** * @author Eric Zhao * @since 1.4.0 */ public interface ServerChangeObserver { /** * Callback on remote server address change. * * @param assignConfig new cluster assignment config */ void onRemoteServerChange(ClusterClientAssignConfig assignConfig); } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.handler; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.client.ClientConstants; import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Netty client handler for Sentinel token client. * * @author Eric Zhao * @since 1.4.0 */ public class TokenClientHandler extends ChannelInboundHandlerAdapter { private final AtomicInteger currentState; private final Runnable disconnectCallback; public TokenClientHandler(AtomicInteger currentState, Runnable disconnectCallback) { this.currentState = currentState; this.disconnectCallback = disconnectCallback; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { currentState.set(ClientConstants.CLIENT_STATUS_STARTED); fireClientPing(ctx); RecordLog.info("[TokenClientHandler] Client handler active, remote address: {}", getRemoteAddress(ctx)); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ClusterResponse) { ClusterResponse response = (ClusterResponse) msg; if (response.getType() == ClusterConstants.MSG_TYPE_PING) { handlePingResponse(ctx, response); return; } TokenClientPromiseHolder.completePromise(response.getId(), response); } } private void fireClientPing(ChannelHandlerContext ctx) { // Data body: namespace of the client. ClusterRequest ping = new ClusterRequest().setId(0) .setType(ClusterConstants.MSG_TYPE_PING) .setData(ConfigSupplierRegistry.getNamespaceSupplier().get()); ctx.writeAndFlush(ping); } private void handlePingResponse(ChannelHandlerContext ctx, ClusterResponse response) { if (response.getStatus() == ClusterConstants.RESPONSE_STATUS_OK) { int count = (int) response.getData(); RecordLog.info("[TokenClientHandler] Client ping OK (target server: {}, connected count: {})", getRemoteAddress(ctx), count); } else { RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {})", getRemoteAddress(ctx)); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { RecordLog.warn("[TokenClientHandler] Client exception caught", cause); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: {}", getRemoteAddress(ctx)); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: {}", getRemoteAddress(ctx)); currentState.set(ClientConstants.CLIENT_STATUS_OFF); disconnectCallback.run(); } private String getRemoteAddress(ChannelHandlerContext ctx) { if (ctx.channel().remoteAddress() == null) { return null; } InetSocketAddress inetAddress = (InetSocketAddress) ctx.channel().remoteAddress(); return inetAddress.getAddress().getHostAddress() + ":" + inetAddress.getPort(); } public int getCurrentState() { return currentState.get(); } public boolean hasStarted() { return getCurrentState() == ClientConstants.CLIENT_STATUS_STARTED; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.handler; import java.util.AbstractMap.SimpleEntry; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import io.netty.channel.ChannelPromise; /** * @author Eric Zhao * @since 1.4.0 */ public final class TokenClientPromiseHolder { private static final Map> PROMISE_MAP = new ConcurrentHashMap<>(); public static void putPromise(int xid, ChannelPromise promise) { PROMISE_MAP.put(xid, new SimpleEntry(promise, null)); } public static SimpleEntry getEntry(int xid) { return PROMISE_MAP.get(xid); } public static void remove(int xid) { PROMISE_MAP.remove(xid); } public static boolean completePromise(int xid, ClusterResponse response) { if (!PROMISE_MAP.containsKey(xid)) { return false; } SimpleEntry entry = PROMISE_MAP.get(xid); if (entry != null) { ChannelPromise promise = entry.getKey(); if (promise.isDone() || promise.isCancelled()) { return false; } entry.setValue(response); promise.setSuccess(); return true; } return false; } private TokenClientPromiseHolder() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.init; import com.alibaba.csp.sentinel.cluster.client.ClientConstants; import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowRequestDataWriter; import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowResponseDataDecoder; import com.alibaba.csp.sentinel.cluster.client.codec.data.ParamFlowRequestDataWriter; import com.alibaba.csp.sentinel.cluster.client.codec.data.PingRequestDataWriter; import com.alibaba.csp.sentinel.cluster.client.codec.data.PingResponseDataDecoder; import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry; import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientStartUpConfig; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.init.InitOrder; /** * @author Eric Zhao * @since 1.4.0 */ @InitOrder(0) public class DefaultClusterClientInitFunc implements InitFunc { @Override public void init() throws Exception { initDefaultEntityWriters(); initDefaultEntityDecoders(); } private void initDefaultEntityWriters() { RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PING, new PingRequestDataWriter()); RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_FLOW, new FlowRequestDataWriter()); Integer maxParamByteSize = ClusterClientStartUpConfig.getMaxParamByteSize(); if (maxParamByteSize == null) { RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter()); } else { RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter(maxParamByteSize)); } } private void initDefaultEntityDecoders() { ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PING, new PingResponseDataDecoder()); ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_FLOW, new FlowResponseDataDecoder()); ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PARAM_FLOW, new FlowResponseDataDecoder()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/entity/ClusterClientStateEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.entity; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterClientStateEntity { private String serverHost; private Integer serverPort; private Integer clientState; private Integer requestTimeout; public String getServerHost() { return serverHost; } public ClusterClientStateEntity setServerHost(String serverHost) { this.serverHost = serverHost; return this; } public Integer getServerPort() { return serverPort; } public ClusterClientStateEntity setServerPort(Integer serverPort) { this.serverPort = serverPort; return this; } public Integer getRequestTimeout() { return requestTimeout; } public ClusterClientStateEntity setRequestTimeout(Integer requestTimeout) { this.requestTimeout = requestTimeout; return this; } public Integer getClientState() { return clientState; } public ClusterClientStateEntity setClientState(Integer clientState) { this.clientState = clientState; return this; } public ClusterClientConfig toClientConfig() { return new ClusterClientConfig().setRequestTimeout(requestTimeout); } public ClusterClientAssignConfig toAssignConfig() { return new ClusterClientAssignConfig() .setServerHost(serverHost) .setServerPort(serverPort); } @Override public String toString() { return "ClusterClientStateEntity{" + "serverHost='" + serverHost + '\'' + ", serverPort=" + serverPort + ", clientState=" + clientState + ", requestTimeout=" + requestTimeout + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.cluster.client.ClientConstants; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.command.entity.ClusterClientStateEntity; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/client/fetchConfig", desc = "get cluster client config") public class FetchClusterClientConfigHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { ClusterClientStateEntity stateVO = new ClusterClientStateEntity() .setServerHost(ClusterClientConfigManager.getServerHost()) .setServerPort(ClusterClientConfigManager.getServerPort()) .setRequestTimeout(ClusterClientConfigManager.getRequestTimeout()); if (TokenClientProvider.isClientSpiAvailable()) { stateVO.setClientState(TokenClientProvider.getClient().getState()); } else { stateVO.setClientState(ClientConstants.CLIENT_STATUS_OFF); } return CommandResponse.ofSuccess(JSON.toJSONString(stateVO)); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.net.URLDecoder; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.command.CommandConstants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.command.entity.ClusterClientStateEntity; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/client/modifyConfig", desc = "modify cluster client config") public class ModifyClusterClientConfigHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); } try { data = URLDecoder.decode(data, "utf-8"); RecordLog.info("[ModifyClusterClientConfigHandler] Receiving cluster client config: {}", data); ClusterClientStateEntity entity = JSON.parseObject(data, ClusterClientStateEntity.class); ClusterClientConfigManager.applyNewConfig(entity.toClientConfig()); ClusterClientConfigManager.applyNewAssignConfig(entity.toAssignConfig()); return CommandResponse.ofSuccess(CommandConstants.MSG_SUCCESS); } catch (Exception e) { RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); return CommandResponse.ofFailure(e, "decode client cluster config error"); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient ================================================ com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter ================================================ com.alibaba.csp.sentinel.cluster.client.codec.DefaultRequestEntityWriter ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder ================================================ com.alibaba.csp.sentinel.cluster.client.codec.DefaultResponseEntityDecoder ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler com.alibaba.csp.sentinel.command.handler.FetchClusterClientConfigHandler ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.Assert; import org.junit.Test; public class FlowResponseDataDecoderTest { @Test public void testDecode() { ByteBuf buf = Unpooled.buffer(); FlowResponseDataDecoder decoder = new FlowResponseDataDecoder(); FlowTokenResponseData data = new FlowTokenResponseData(); data.setRemainingCount(12); data.setWaitInMs(13); buf.writeInt(12); buf.writeInt(13); Assert.assertEquals(decoder.decode(buf), data); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriterTest.java ================================================ package com.alibaba.csp.sentinel.cluster.client.codec.data; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ParamFlowRequestDataWriterTest { @Test public void testCalculateParamTransportSize() { ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter(); // POJO (non-primitive type) should not be regarded as a valid parameter. assertEquals(0, writer.calculateParamTransportSize(new SomePojo().setParam1("abc"))); assertEquals(4 + 1, writer.calculateParamTransportSize(1)); assertEquals(1 + 1, writer.calculateParamTransportSize((byte) 1)); assertEquals(1 + 1, writer.calculateParamTransportSize(false)); assertEquals(8 + 1, writer.calculateParamTransportSize(2L)); assertEquals(8 + 1, writer.calculateParamTransportSize(4.0d)); final String paramStr = "Sentinel"; assertEquals(1 + 4 + paramStr.getBytes().length, writer.calculateParamTransportSize(paramStr)); } @Test public void testResolveValidParams() { final int maxSize = 15; ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter(maxSize); ArrayList params = new ArrayList() {{ add(1); add(64); add(3); }}; List validParams = writer.resolveValidParams(params); assertTrue(validParams.contains(1) && validParams.contains(64) && validParams.contains(3)); //when over maxSize, the exceed number should not be contained params.add(5); assertFalse(writer.resolveValidParams(params).contains(5)); //POJO (non-primitive type) should not be regarded as a valid parameter assertTrue(writer.resolveValidParams(new ArrayList() {{ add(new SomePojo()); }}).size() == 0); } private static class SomePojo { private String param1; public String getParam1() { return param1; } public SomePojo setParam1(String param1) { this.param1 = param1; return this; } @Override public String toString() { return "SomePojo{" + "param1='" + param1 + '\'' + '}'; } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client.codec.data; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class PingResponseDataDecoderTest { @Test public void testDecodePingResponseData() { ByteBuf buf = Unpooled.buffer(); PingResponseDataDecoder decoder = new PingResponseDataDecoder(); int big = Integer.MAX_VALUE; buf.writeInt(big); assertThat(decoder.decode(buf)).isEqualTo(big); byte small = 12; buf.writeByte(small); assertThat(decoder.decode(buf)).isEqualTo(small); buf.release(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/pom.xml ================================================ com.alibaba.csp sentinel-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-cluster-common-default jar com.alibaba.csp sentinel-core ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterConstants { public static final int MSG_TYPE_PING = 0; public static final int MSG_TYPE_FLOW = 1; public static final int MSG_TYPE_PARAM_FLOW = 2; public static final int MSG_TYPE_CONCURRENT_FLOW_ACQUIRE = 3; public static final int MSG_TYPE_CONCURRENT_FLOW_RELEASE = 4; public static final int RESPONSE_STATUS_BAD = -1; public static final int RESPONSE_STATUS_OK = 0; public static final int PARAM_TYPE_INTEGER = 0; public static final int PARAM_TYPE_LONG = 1; public static final int PARAM_TYPE_BYTE = 2; public static final int PARAM_TYPE_DOUBLE = 3; public static final int PARAM_TYPE_FLOAT = 4; public static final int PARAM_TYPE_SHORT = 5; public static final int PARAM_TYPE_BOOLEAN = 6; public static final int PARAM_TYPE_STRING = 7; public static final int DEFAULT_CLUSTER_SERVER_PORT = 18730; public static final int DEFAULT_REQUEST_TIMEOUT = 20; public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 10 * 1000; private ClusterConstants() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; /** * @author jialiang.ljl * @since 1.4.0 */ public final class ClusterErrorMessages { public static final String BAD_REQUEST = "bad request"; public static final String UNEXPECTED_STATUS = "unexpected status"; public static final String TOO_MANY_REQUESTS = "too many requests (client side)"; public static final String REQUEST_TIME_OUT = "request time out"; public static final String CLIENT_NOT_READY = "client not ready"; public static final String NO_RULES_IN_SERVER = "no rules in token server"; private ClusterErrorMessages() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; /** * Synchronous transport client for distributed flow control. * * @author Eric Zhao * @since 1.4.0 */ public interface ClusterTransportClient { /** * Start the client. * * @throws Exception some error occurred (e.g. initialization failed) */ void start() throws Exception; /** * Stop the client. * * @throws Exception some error occurred (e.g. shutdown failed) */ void stop() throws Exception; /** * Send request to remote server and get response. * * @param request Sentinel cluster request * @return response from remote server * @throws Exception some error occurs */ ClusterResponse sendRequest(ClusterRequest request) throws Exception; /** * Check whether the client has been started and ready for sending requests. * * @return true if the client is ready to send requests, otherwise false */ boolean isReady(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Request type annotation for handlers, codes, etc. * * @author Eric Zhao * @since 1.4.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface RequestType { /** * Type of the request to handle. * * @return type of the request */ int value(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec; /** * @param source stream type * @param target entity type * @author Eric Zhao * @since 1.4.0 */ public interface EntityDecoder { /** * Decode target object from source stream. * * @param source source stream * @return decoded target object */ T decode(S source); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec; /** * A universal interface for publishing entities to a target stream. * * @param entity type * @param target stream type * @author Eric Zhao * @since 1.4.0 */ public interface EntityWriter { /** * Write the provided entity to target stream. * * @param entity entity to publish * @param target the target stream */ void writeTo(E entity, T target); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec.request; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.request.Request; /** * @author Eric Zhao * @since 1.4.0 */ public interface RequestEntityDecoder extends EntityDecoder { } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec.request; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.request.Request; /** * A universal {@link EntityWriter} interface for publishing {@link Request} to a target stream. * * @author Eric Zhao * @since 1.4.0 */ public interface RequestEntityWriter extends EntityWriter { } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec.response; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.response.Response; /** * @author Eric Zhao * @since 1.4.0 */ public interface ResponseEntityDecoder extends EntityDecoder { } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.codec.response; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.response.Response; /** * A universal {@link EntityWriter} interface for publishing {@link Response} to a target stream. * * @author Eric Zhao * @since 1.4.0 */ public interface ResponseEntityWriter extends EntityWriter { } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.exception; /** * @author jialiang.ljl * @since 1.4.0 */ public class SentinelClusterException extends Exception { public SentinelClusterException(String errorMsg) { super(errorMsg); } @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.registry; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Supplier; /** * @author Eric Zhao * @since 1.4.0 */ public final class ConfigSupplierRegistry { /** * The default namespace supplier provides appName as namespace. */ private static final Supplier DEFAULT_APP_NAME_SUPPLIER = new Supplier() { @Override public String get() { return AppNameUtil.getAppName(); } }; /** * Registered namespace supplier. */ private static Supplier namespaceSupplier = DEFAULT_APP_NAME_SUPPLIER; /** * Get the registered namespace supplier. * * @return the registered namespace supplier */ public static Supplier getNamespaceSupplier() { return namespaceSupplier; } public static void setNamespaceSupplier(Supplier namespaceSupplier) { AssertUtil.notNull(namespaceSupplier, "namespaceSupplier cannot be null"); ConfigSupplierRegistry.namespaceSupplier = namespaceSupplier; RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: {}", namespaceSupplier.get()); } private ConfigSupplierRegistry() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.request; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterRequest implements Request { private int id; private int type; private T data; public ClusterRequest() {} public ClusterRequest(int id, int type, T data) { this.id = id; this.type = type; this.data = data; } public ClusterRequest(int type, T data) { this.type = type; this.data = data; } @Override public int getId() { return id; } public ClusterRequest setId(int id) { this.id = id; return this; } @Override public int getType() { return type; } public ClusterRequest setType(int type) { this.type = type; return this; } public T getData() { return data; } public ClusterRequest setData(T data) { this.data = data; return this; } @Override public String toString() { return "ClusterRequest{" + "id=" + id + ", type=" + type + ", data=" + data + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.request; /** * Cluster transport request interface. * * @author Eric Zhao * @since 1.4.0 */ public interface Request { /** * Get request type. * * @return request type */ int getType(); /** * Get request ID. * * @return unique request ID */ int getId(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.request.data; /** * @author Eric Zhao * @since 1.4.0 */ public class FlowRequestData { private long flowId; private int count; private boolean priority; public long getFlowId() { return flowId; } public FlowRequestData setFlowId(long flowId) { this.flowId = flowId; return this; } public int getCount() { return count; } public FlowRequestData setCount(int count) { this.count = count; return this; } public boolean isPriority() { return priority; } public FlowRequestData setPriority(boolean priority) { this.priority = priority; return this; } @Override public String toString() { return "FlowRequestData{" + "flowId=" + flowId + ", count=" + count + ", priority=" + priority + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.request.data; import java.util.Collection; /** * @author Eric Zhao * @since 1.4.0 */ public class ParamFlowRequestData { private long flowId; private int count; private Collection params; public long getFlowId() { return flowId; } public ParamFlowRequestData setFlowId(long flowId) { this.flowId = flowId; return this; } public int getCount() { return count; } public ParamFlowRequestData setCount(int count) { this.count = count; return this; } public Collection getParams() { return params; } public ParamFlowRequestData setParams(Collection params) { this.params = params; return this; } @Override public String toString() { return "ParamFlowRequestData{" + "flowId=" + flowId + ", count=" + count + ", params=" + params + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.response; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterResponse implements Response { private int id; private int type; private int status; private T data; public ClusterResponse() {} public ClusterResponse(int id, int type, int status, T data) { this.id = id; this.type = type; this.status = status; this.data = data; } @Override public int getId() { return id; } public ClusterResponse setId(int id) { this.id = id; return this; } @Override public int getType() { return type; } public ClusterResponse setType(int type) { this.type = type; return this; } @Override public int getStatus() { return status; } public ClusterResponse setStatus(int status) { this.status = status; return this; } public T getData() { return data; } public ClusterResponse setData(T data) { this.data = data; return this; } @Override public String toString() { return "ClusterResponse{" + "id=" + id + ", type=" + type + ", status=" + status + ", data=" + data + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.response; /** * Cluster transport response interface. * * @author Eric Zhao * @since 1.4.0 */ public interface Response { /** * Get response ID. * * @return response ID */ int getId(); /** * Get response type. * * @return response type */ int getType(); /** * Get response status. * * @return response status */ int getStatus(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.response.data; /** * @author Eric Zhao * @since 1.4.0 */ public class FlowTokenResponseData { private int remainingCount; private int waitInMs; public int getRemainingCount() { return remainingCount; } public FlowTokenResponseData setRemainingCount(int remainingCount) { this.remainingCount = remainingCount; return this; } public int getWaitInMs() { return waitInMs; } public FlowTokenResponseData setWaitInMs(int waitInMs) { this.waitInMs = waitInMs; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof FlowTokenResponseData)) { return false; } FlowTokenResponseData that = (FlowTokenResponseData) o; return this.remainingCount == that.remainingCount && this.waitInMs == that.waitInMs; } @Override public int hashCode() { int result = remainingCount; result = 31 * result + waitInMs; return result; } @Override public String toString() { return "FlowTokenResponseData{" + "remainingCount=" + remainingCount + ", waitInMs=" + waitInMs + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/pom.xml ================================================ com.alibaba.csp sentinel-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-cluster-server-default jar com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-common provided com.alibaba.csp sentinel-cluster-common-default com.alibaba.csp sentinel-parameter-flow-control io.netty netty-handler com.alibaba.csp sentinel-datasource-nacos test junit junit test org.mockito mockito-inline test org.assertj assertj-core test ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; /** * Flow checker for cluster flow rules. * * @author Eric Zhao * @since 1.4.0 */ final class ClusterFlowChecker { private static double calcGlobalThreshold(FlowRule rule) { double count = rule.getCount(); switch (rule.getClusterConfig().getThresholdType()) { case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL: return count; case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: default: int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); return count * connectedCount; } } static boolean allowProceed(long flowId) { String namespace = ClusterFlowRuleManager.getNamespace(flowId); return GlobalRequestLimiter.tryPass(namespace); } static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) { Long id = rule.getClusterConfig().getFlowId(); if (!allowProceed(id)) { return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST); } ClusterMetric metric = ClusterMetricStatistics.getMetric(id); if (metric == null) { return new TokenResult(TokenResultStatus.FAIL); } double latestQps = metric.getAvg(ClusterFlowEvent.PASS); double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount(); double nextRemaining = globalThreshold - latestQps - acquireCount; if (nextRemaining >= 0) { // TODO: checking logic and metric operation should be separated. metric.add(ClusterFlowEvent.PASS, acquireCount); metric.add(ClusterFlowEvent.PASS_REQUEST, 1); if (prioritized) { // Add prioritized pass. metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount); } // Remaining count is cut down to a smaller integer. return new TokenResult(TokenResultStatus.OK) .setRemaining((int) nextRemaining) .setWaitInMs(0); } else { if (prioritized) { // Try to occupy incoming buckets. double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING); if (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) { int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold); // waitInMs > 0 indicates pre-occupy incoming buckets successfully. if (waitInMs > 0) { ClusterServerStatLogUtil.log("flow|waiting|" + id); return new TokenResult(TokenResultStatus.SHOULD_WAIT) .setRemaining(0) .setWaitInMs(waitInMs); } // Or else occupy failed, should be blocked. } } // Blocked. metric.add(ClusterFlowEvent.BLOCK, acquireCount); metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1); ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount); ClusterServerStatLogUtil.log("flow|block_request|" + id, 1); if (prioritized) { // Add prioritized block. metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount); ClusterServerStatLogUtil.log("flow|occupied_block|" + id, 1); } return blockedResult(); } } private static TokenResult blockedResult() { return new TokenResult(TokenResultStatus.BLOCKED) .setRemaining(0) .setWaitInMs(0); } private ClusterFlowChecker() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import java.util.Collection; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; /** * @author jialiang.linjl * @author Eric Zhao * @since 1.4.0 */ public final class ClusterParamFlowChecker { static boolean allowProceed(long flowId) { String namespace = ClusterParamFlowRuleManager.getNamespace(flowId); return GlobalRequestLimiter.tryPass(namespace); } static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection values) { Long id = rule.getClusterConfig().getFlowId(); if (!allowProceed(id)) { return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST); } ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id); if (metric == null) { // Unexpected state, return FAIL. return new TokenResult(TokenResultStatus.FAIL); } if (values == null || values.isEmpty()) { // Empty parameter list will always pass. return new TokenResult(TokenResultStatus.OK); } double remaining = -1; boolean hasPassed = true; Object blockObject = null; for (Object value : values) { double latestQps = metric.getAvg(value); double threshold = calcGlobalThreshold(rule, value); double nextRemaining = threshold - latestQps - count; remaining = nextRemaining; if (nextRemaining < 0) { hasPassed = false; blockObject = value; break; } } if (hasPassed) { for (Object value : values) { metric.addValue(value, count); } ClusterServerStatLogUtil.log(String.format("param|pass|%d", id)); } else { ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject)); } if (values.size() > 1) { // Remaining field is unsupported for multi-values. remaining = -1; } return hasPassed ? newPassResponse((int)remaining): newBlockResponse(); } private static TokenResult newPassResponse(int remaining) { return new TokenResult(TokenResultStatus.OK) .setRemaining(remaining) .setWaitInMs(0); } private static TokenResult newBlockResponse() { return new TokenResult(TokenResultStatus.BLOCKED) .setRemaining(0) .setWaitInMs(0); } private static double calcGlobalThreshold(ParamFlowRule rule, Object value) { double count = getRawThreshold(rule, value); switch (rule.getClusterConfig().getThresholdType()) { case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL: return count; case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: default: int connectedCount = ClusterParamFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); return count * connectedCount; } } private static double getRawThreshold(ParamFlowRule rule, Object value) { Integer itemCount = rule.retrieveExclusiveItemCount(value); if (itemCount == null) { return rule.getCount(); } else { return itemCount; } } private ClusterParamFlowChecker() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager; import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import java.util.concurrent.atomic.AtomicInteger; /** * @author yunfeiyanggzq */ final public class ConcurrentClusterFlowChecker { public static double calcGlobalThreshold(FlowRule rule) { double count = rule.getCount(); switch (rule.getClusterConfig().getThresholdType()) { case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL: return count; case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: default: int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); return count * connectedCount; } } public static TokenResult acquireConcurrentToken(/*@Valid*/ String clientAddress, FlowRule rule, int acquireCount) { long flowId = rule.getClusterConfig().getFlowId(); AtomicInteger nowCalls = CurrentConcurrencyManager.get(flowId); if (nowCalls == null) { RecordLog.warn("[ConcurrentClusterFlowChecker] Fail to get nowCalls by flowId<{}>", flowId); return new TokenResult(TokenResultStatus.FAIL); } // check before enter the lock to improve the efficiency if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) { ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount); return new TokenResult(TokenResultStatus.BLOCKED); } // ensure the atomicity of operations // lock different nowCalls to improve the efficiency synchronized (nowCalls) { // check again whether the request can pass. if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) { ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount); return new TokenResult(TokenResultStatus.BLOCKED); } else { nowCalls.getAndAdd(acquireCount); } } ClusterServerStatLogUtil.log("concurrent|pass|" + flowId, acquireCount); TokenCacheNode node = TokenCacheNode.generateTokenCacheNode(rule, acquireCount, clientAddress); TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node); TokenResult tokenResult = new TokenResult(TokenResultStatus.OK); tokenResult.setTokenId(node.getTokenId()); return tokenResult; } public static TokenResult releaseConcurrentToken(/*@Valid*/ long tokenId) { TokenCacheNode node = TokenCacheNodeManager.getTokenCacheNode(tokenId); if (node == null) { RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released", tokenId); return new TokenResult(TokenResultStatus.ALREADY_RELEASE); } FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()); if (rule == null) { RecordLog.info("[ConcurrentClusterFlowChecker] Fail to get rule by flowId<{}>", node.getFlowId()); return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); } if (TokenCacheNodeManager.removeTokenCacheNode(tokenId) == null) { RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released for flowId<{}>", tokenId, node.getFlowId()); return new TokenResult(TokenResultStatus.ALREADY_RELEASE); } int acquireCount = node.getAcquireCount(); AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId()); nowCalls.getAndAdd(-1 * acquireCount); ClusterServerStatLogUtil.log("concurrent|release|" + rule.getClusterConfig().getFlowId(), acquireCount); return new TokenResult(TokenResultStatus.RELEASE_OK); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.spi.Spi; import java.util.Collection; /** * Default implementation for cluster {@link TokenService}. * * @author Eric Zhao * @since 1.4.0 */ @Spi(isDefault = true) public class DefaultTokenService implements TokenService { @Override public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) { if (notValidRequest(ruleId, acquireCount)) { return badRequest(); } // The rule should be valid. FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId); if (rule == null) { return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); } return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized); } @Override public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params) { if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) { return badRequest(); } // The rule should be valid. ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId); if (rule == null) { return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); } return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params); } @Override public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) { if (notValidRequest(clientAddress, ruleId, acquireCount)) { return badRequest(); } // The rule should be valid. FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId); if (rule == null) { return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); } return ConcurrentClusterFlowChecker.acquireConcurrentToken(clientAddress, rule, acquireCount); } @Override public void releaseConcurrentToken(Long tokenId) { if (tokenId == null) { return; } ConcurrentClusterFlowChecker.releaseConcurrentToken(tokenId); } private boolean notValidRequest(Long id, int count) { return id == null || id <= 0 || count <= 0; } private boolean notValidRequest(String address, Long id, int count) { return address == null || "".equals(address) || id == null || id <= 0 || count <= 0; } private TokenResult badRequest() { return new TokenResult(TokenResultStatus.BAD_REQUEST); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.rule; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Manager for cluster flow rules. * * @author Eric Zhao * @since 1.4.0 */ public final class ClusterFlowRuleManager { /** * The default cluster flow rule property supplier that creates a new dynamic property * for a specific namespace to do rule management manually. */ public static final Function>> DEFAULT_PROPERTY_SUPPLIER = new Function>>() { @Override public SentinelProperty> apply(String namespace) { return new DynamicSentinelProperty<>(); } }; /** * (flowId, clusterRule) */ private static final Map FLOW_RULES = new ConcurrentHashMap<>(); /** * (namespace, [flowId...]) */ private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); /** *

This map (flowId, namespace) is used for getting connected count * when checking a specific rule in {@code ruleId}:

* *
     * ruleId -> namespace -> connection group -> connected count
     * 
*/ private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); /** * (namespace, property-listener wrapper) */ private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); /** * Cluster flow rule property supplier for a specific namespace. */ private static volatile Function>> propertySupplier = DEFAULT_PROPERTY_SUPPLIER; private static final Object UPDATE_LOCK = new Object(); static { initDefaultProperty(); } private static void initDefaultProperty() { // The server should always support default namespace, // so register a default property for default namespace. SentinelProperty> defaultProperty = new DynamicSentinelProperty<>(); String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; registerPropertyInternal(defaultNamespace, defaultProperty); } public static void setPropertySupplier(Function>> propertySupplier) { AssertUtil.notNull(propertySupplier, "flow rule property supplier cannot be null"); ClusterFlowRuleManager.propertySupplier = propertySupplier; } /** * Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s. * The property is the source of cluster {@link FlowRule}s for a specific namespace. * * @param namespace namespace to register */ public static void register2Property(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (propertySupplier == null) { RecordLog.warn( "[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property"); return; } SentinelProperty> property = propertySupplier.apply(namespace); if (property == null) { RecordLog.warn( "[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring"); return; } synchronized (UPDATE_LOCK) { RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager" + " for namespace <{}>", namespace); registerPropertyInternal(namespace, property); } } /** * Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s if current property for namespace is absent. * The property is the source of cluster {@link FlowRule}s for a specific namespace. * * @param namespace namespace to register */ public static void registerPropertyIfAbsent(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (!PROPERTY_MAP.containsKey(namespace)) { synchronized (UPDATE_LOCK) { if (!PROPERTY_MAP.containsKey(namespace)) { register2Property(namespace); } } } } private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/ SentinelProperty> property) { NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); if (oldProperty != null) { oldProperty.getProperty().removeListener(oldProperty.getListener()); } PropertyListener> listener = new FlowRulePropertyListener(namespace); property.addListener(listener); PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet == null) { resetNamespaceFlowIdMapFor(namespace); } } /** * Remove cluster flow rule property for a specific namespace. * * @param namespace valid namespace */ public static void removeProperty(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); synchronized (UPDATE_LOCK) { NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); if (property != null) { property.getProperty().removeListener(property.getListener()); PROPERTY_MAP.remove(namespace); } RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager" + " for namespace <{}>", namespace); } } private static void removePropertyListeners() { for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { property.getProperty().removeListener(property.getListener()); } } private static void restorePropertyListeners() { for (NamespaceFlowProperty p : PROPERTY_MAP.values()) { p.getProperty().removeListener(p.getListener()); p.getProperty().addListener(p.getListener()); } } /** * Get flow rule by rule ID. * * @param id rule ID * @return flow rule */ public static FlowRule getFlowRuleById(Long id) { if (!ClusterRuleUtil.validId(id)) { return null; } return FLOW_RULES.get(id); } public static Set getFlowIdSet(String namespace) { if (StringUtil.isEmpty(namespace)) { return new HashSet<>(); } Set set = NAMESPACE_FLOW_ID_MAP.get(namespace); if (set == null) { return new HashSet<>(); } return new HashSet<>(set); } public static List getAllFlowRules() { return new ArrayList<>(FLOW_RULES.values()); } /** * Get all cluster flow rules within a specific namespace. * * @param namespace valid namespace * @return cluster flow rules within the provided namespace */ public static List getFlowRules(String namespace) { if (StringUtil.isEmpty(namespace)) { return new ArrayList<>(); } List rules = new ArrayList<>(); Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet == null || flowIdSet.isEmpty()) { return rules; } for (Long flowId : flowIdSet) { FlowRule rule = FLOW_RULES.get(flowId); if (rule != null) { rules.add(rule); } } return rules; } /** * Load flow rules for a specific namespace. The former rules of the namespace will be replaced. * * @param namespace a valid namespace * @param rules rule list */ public static void loadRules(String namespace, List rules) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); if (property != null) { property.getProperty().updateValue(rules); } } private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet()); } /** * Clear all rules of the provided namespace and reset map. * * @param namespace valid namespace */ private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet != null && !flowIdSet.isEmpty()) { for (Long flowId : flowIdSet) { FLOW_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); if (CurrentConcurrencyManager.containsFlowId(flowId)) { CurrentConcurrencyManager.remove(flowId); } } flowIdSet.clear(); } else { resetNamespaceFlowIdMapFor(namespace); } } private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate predicate) { Set oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (oldIdSet != null && !oldIdSet.isEmpty()) { for (Long flowId : oldIdSet) { if (predicate.test(flowId)) { FLOW_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); ClusterMetricStatistics.removeMetric(flowId); if (CurrentConcurrencyManager.containsFlowId(flowId)) { CurrentConcurrencyManager.remove(flowId); } } } oldIdSet.clear(); } } /** * Get connected count for associated namespace of given {@code flowId}. * * @param flowId unique flow ID * @return connected count */ public static int getConnectedCount(long flowId) { if (flowId <= 0) { return 0; } String namespace = FLOW_NAMESPACE_MAP.get(flowId); if (namespace == null) { return 0; } return ConnectionManager.getConnectedCount(namespace); } public static String getNamespace(long flowId) { return FLOW_NAMESPACE_MAP.get(flowId); } private static void applyClusterFlowRule(List list, /*@Valid*/ String namespace) { if (list == null || list.isEmpty()) { clearAndResetRulesFor(namespace); return; } final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); Set flowIdSet = new HashSet<>(); for (FlowRule rule : list) { if (!rule.isClusterMode()) { continue; } if (!FlowRuleUtil.isValidRule(rule)) { RecordLog.warn( "[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } // Flow id should not be null after filtered. ClusterFlowConfig clusterConfig = rule.getClusterConfig(); Long flowId = clusterConfig.getFlowId(); if (flowId == null) { continue; } ruleMap.put(flowId, rule); FLOW_NAMESPACE_MAP.put(flowId, namespace); flowIdSet.add(flowId); if (!CurrentConcurrencyManager.containsFlowId(flowId)) { CurrentConcurrencyManager.put(flowId, 0); } // Prepare cluster metric from valid flow ID. ClusterMetricStatistics.putMetricIfAbsent(flowId, new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())); } // Cleanup unused cluster metrics. clearAndResetRulesConditional(namespace, new Predicate() { @Override public boolean test(Long flowId) { return !ruleMap.containsKey(flowId); } }); FLOW_RULES.putAll(ruleMap); NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet); } private static final class FlowRulePropertyListener implements PropertyListener> { private final String namespace; public FlowRulePropertyListener(String namespace) { this.namespace = namespace; } @Override public synchronized void configUpdate(List conf) { applyClusterFlowRule(conf, namespace); RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}", namespace, FLOW_RULES); } @Override public synchronized void configLoad(List conf) { applyClusterFlowRule(conf, namespace); RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}", namespace, FLOW_RULES); } } private ClusterFlowRuleManager() { } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.rule; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; /** * Manager for cluster parameter flow rules. * * @author Eric Zhao * @since 1.4.0 */ public final class ClusterParamFlowRuleManager { /** * The default cluster parameter flow rule property supplier that creates a new * dynamic property for a specific namespace to manually do rule management. */ public static final Function>> DEFAULT_PROPERTY_SUPPLIER = new Function>>() { @Override public SentinelProperty> apply(String namespace) { return new DynamicSentinelProperty<>(); } }; /** * (id, clusterParamRule) */ private static final Map PARAM_RULES = new ConcurrentHashMap<>(); /** * (namespace, [flowId...]) */ private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); /** * (flowId, namespace) */ private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); /** * (namespace, property-listener wrapper) */ private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); /** * Cluster parameter flow rule property supplier for a specific namespace. */ private static volatile Function>> propertySupplier = DEFAULT_PROPERTY_SUPPLIER; private static final Object UPDATE_LOCK = new Object(); static { initDefaultProperty(); } private static void initDefaultProperty() { SentinelProperty> defaultProperty = new DynamicSentinelProperty<>(); String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; registerPropertyInternal(defaultNamespace, defaultProperty); } public static void setPropertySupplier( Function>> propertySupplier) { ClusterParamFlowRuleManager.propertySupplier = propertySupplier; } public static String getNamespace(long flowId) { return FLOW_NAMESPACE_MAP.get(flowId); } /** * Listen to the {@link SentinelProperty} for cluster {@link ParamFlowRule}s. * The property is the source of cluster {@link ParamFlowRule}s for a specific namespace. * * @param namespace namespace to register */ public static void register2Property(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (propertySupplier == null) { RecordLog.warn( "[ClusterParamFlowRuleManager] Cluster param rule property supplier is absent, cannot register " + "property"); return; } SentinelProperty> property = propertySupplier.apply(namespace); if (property == null) { RecordLog.warn( "[ClusterParamFlowRuleManager] Wrong created property from cluster param rule property supplier, " + "ignoring"); return; } synchronized (UPDATE_LOCK) { RecordLog.info("[ClusterParamFlowRuleManager] Registering new property to cluster param rule manager" + " for namespace <{}>", namespace); registerPropertyInternal(namespace, property); } } public static void registerPropertyIfAbsent(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (!PROPERTY_MAP.containsKey(namespace)) { synchronized (UPDATE_LOCK) { if (!PROPERTY_MAP.containsKey(namespace)) { register2Property(namespace); } } } } private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/ SentinelProperty> property) { NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); if (oldProperty != null) { oldProperty.getProperty().removeListener(oldProperty.getListener()); } PropertyListener> listener = new ParamRulePropertyListener(namespace); property.addListener(listener); PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet == null) { resetNamespaceFlowIdMapFor(namespace); } } public static void removeProperty(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); synchronized (UPDATE_LOCK) { NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); if (property != null) { property.getProperty().removeListener(property.getListener()); PROPERTY_MAP.remove(namespace); } RecordLog.info("[ClusterParamFlowRuleManager] Removing property from cluster flow rule manager" + " for namespace <{}>", namespace); } } private static void removePropertyListeners() { for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { property.getProperty().removeListener(property.getListener()); } } private static void restorePropertyListeners() { for (NamespaceFlowProperty p : PROPERTY_MAP.values()) { p.getProperty().removeListener(p.getListener()); p.getProperty().addListener(p.getListener()); } } private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet()); } private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet != null && !flowIdSet.isEmpty()) { for (Long flowId : flowIdSet) { PARAM_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); } flowIdSet.clear(); } else { resetNamespaceFlowIdMapFor(namespace); } } private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate predicate) { Set oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (oldIdSet != null && !oldIdSet.isEmpty()) { for (Long flowId : oldIdSet) { if (predicate.test(flowId)) { PARAM_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); ClusterParamMetricStatistics.removeMetric(flowId); } } oldIdSet.clear(); } } public static ParamFlowRule getParamRuleById(Long id) { if (!ClusterRuleUtil.validId(id)) { return null; } return PARAM_RULES.get(id); } public static Set getFlowIdSet(String namespace) { if (StringUtil.isEmpty(namespace)) { return new HashSet<>(); } Set set = NAMESPACE_FLOW_ID_MAP.get(namespace); if (set == null) { return new HashSet<>(); } return new HashSet<>(set); } public static List getAllParamRules() { return new ArrayList<>(PARAM_RULES.values()); } /** * Get all cluster parameter flow rules within a specific namespace. * * @param namespace a valid namespace * @return cluster parameter flow rules within the provided namespace */ public static List getParamRules(String namespace) { if (StringUtil.isEmpty(namespace)) { return new ArrayList<>(); } List rules = new ArrayList<>(); Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet == null || flowIdSet.isEmpty()) { return rules; } for (Long flowId : flowIdSet) { ParamFlowRule rule = PARAM_RULES.get(flowId); if (rule != null) { rules.add(rule); } } return rules; } /** * Load parameter flow rules for a specific namespace. The former rules of the namespace will be replaced. * * @param namespace a valid namespace * @param rules rule list */ public static void loadRules(String namespace, List rules) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); if (property != null) { property.getProperty().updateValue(rules); } } /** * Get connected count for associated namespace of given {@code flowId}. * * @param flowId existing rule ID * @return connected count */ public static int getConnectedCount(long flowId) { if (flowId <= 0) { return 0; } String namespace = FLOW_NAMESPACE_MAP.get(flowId); if (namespace == null) { return 0; } return ConnectionManager.getConnectedCount(namespace); } private static class ParamRulePropertyListener implements PropertyListener> { private final String namespace; public ParamRulePropertyListener(String namespace) { this.namespace = namespace; } @Override public void configLoad(List conf) { applyClusterParamRules(conf, namespace); RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{}>: {}", namespace, PARAM_RULES); } @Override public void configUpdate(List conf) { applyClusterParamRules(conf, namespace); RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{}>: {}", namespace, PARAM_RULES); } } private static void applyClusterParamRules(List list, /*@Valid*/ String namespace) { if (list == null || list.isEmpty()) { clearAndResetRulesFor(namespace); return; } final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); Set flowIdSet = new HashSet<>(); for (ParamFlowRule rule : list) { if (!rule.isClusterMode()) { continue; } if (!ParamFlowRuleUtil.isValidRule(rule)) { RecordLog.warn( "[ClusterParamFlowRuleManager] Ignoring invalid param flow rule when loading new flow rules: " + rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } ParamFlowRuleUtil.fillExceptionFlowItems(rule); ParamFlowClusterConfig clusterConfig = rule.getClusterConfig(); // Flow id should not be null after filtered. Long flowId = clusterConfig.getFlowId(); if (flowId == null) { continue; } ruleMap.put(flowId, rule); FLOW_NAMESPACE_MAP.put(flowId, namespace); flowIdSet.add(flowId); // Prepare cluster parameter metric from valid rule ID. ClusterParamMetricStatistics.putMetricIfAbsent(flowId, new ClusterParamMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())); } // Cleanup unused cluster parameter metrics. clearAndResetRulesConditional(namespace, new Predicate() { @Override public boolean test(Long flowId) { return !ruleMap.containsKey(flowId); } }); PARAM_RULES.putAll(ruleMap); NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet); } private ClusterParamFlowRuleManager() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.rule; import java.util.List; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; /** * A property wrapper for list of rules of a given namespace. * This is useful for auto-management of the property and listener. * * @param type of the rule * @author Eric Zhao * @since 1.4.0 */ class NamespaceFlowProperty { private final String namespace; private final SentinelProperty> property; private final PropertyListener> listener; public NamespaceFlowProperty(String namespace, SentinelProperty> property, PropertyListener> listener) { this.namespace = namespace; this.property = property; this.listener = listener; } public SentinelProperty> getProperty() { return property; } public String getNamespace() { return namespace; } public PropertyListener> getListener() { return listener; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.Map; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterMetricNode { private long timestamp; private String resourceName; private long flowId; private double passQps; private double blockQps; private long rt; private Map topParams; public long getTimestamp() { return timestamp; } public ClusterMetricNode setTimestamp(long timestamp) { this.timestamp = timestamp; return this; } public String getResourceName() { return resourceName; } public ClusterMetricNode setResourceName(String resourceName) { this.resourceName = resourceName; return this; } public long getFlowId() { return flowId; } public ClusterMetricNode setFlowId(long flowId) { this.flowId = flowId; return this; } public double getPassQps() { return passQps; } public ClusterMetricNode setPassQps(double passQps) { this.passQps = passQps; return this; } public double getBlockQps() { return blockQps; } public ClusterMetricNode setBlockQps(double blockQps) { this.blockQps = blockQps; return this; } public long getRt() { return rt; } public ClusterMetricNode setRt(long rt) { this.rt = rt; return this; } public Map getTopParams() { return topParams; } public ClusterMetricNode setTopParams(Map topParams) { this.topParams = topParams; return this; } @Override public String toString() { return "ClusterMetricNode{" + "timestamp=" + timestamp + ", resourceName='" + resourceName + '\'' + ", flowId=" + flowId + ", passQps=" + passQps + ", blockQps=" + blockQps + ", rt=" + rt + ", topParams=" + topParams + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricNodeGenerator.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterMetricNodeGenerator { public static Map> generateCurrentNodeMap(String namespace) { Map> map = new HashMap<>(); Set flowIds = ClusterFlowRuleManager.getFlowIdSet(namespace); Set paramFlowIds = ClusterParamFlowRuleManager.getFlowIdSet(namespace); for (Long id : flowIds) { ClusterMetricNode node = flowToMetricNode(id); if (node == null) { continue; } putToMap(map, node); } for (Long id : paramFlowIds) { ClusterMetricNode node = paramToMetricNode(id); if (node == null) { continue; } putToMap(map, node); } return map; } private static void putToMap(Map> map, ClusterMetricNode node) { List nodeList = map.get(node.getResourceName()); if (nodeList == null) { nodeList = new ArrayList<>(); map.put(node.getResourceName(), nodeList); } nodeList.add(node); } public static ClusterMetricNode flowToMetricNode(long flowId) { FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId); if (rule == null) { return null; } ClusterMetric metric = ClusterMetricStatistics.getMetric(flowId); if (metric == null) { return new ClusterMetricNode().setFlowId(flowId) .setResourceName(rule.getResource()); } return new ClusterMetricNode() .setFlowId(flowId) .setResourceName(rule.getResource()) .setBlockQps(metric.getAvg(ClusterFlowEvent.BLOCK)) .setPassQps(metric.getAvg(ClusterFlowEvent.PASS)) .setTimestamp(TimeUtil.currentTimeMillis()); } public static ClusterMetricNode paramToMetricNode(long flowId) { ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(flowId); if (rule == null) { return null; } ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(flowId); if (metric == null) { return new ClusterMetricNode().setFlowId(flowId) .setResourceName(rule.getResource()) .setTimestamp(TimeUtil.currentTimeMillis()) .setTopParams(new HashMap(0)); } return new ClusterMetricNode() .setFlowId(flowId) .setResourceName(rule.getResource()) .setTimestamp(TimeUtil.currentTimeMillis()) .setTopParams(metric.getTopValues(5)); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterMetricStatistics { private static final Map METRIC_MAP = new ConcurrentHashMap<>(); public static void clear() { METRIC_MAP.clear(); } public static void putMetric(long id, ClusterMetric metric) { AssertUtil.notNull(metric, "Cluster metric cannot be null"); METRIC_MAP.put(id, metric); } public static boolean putMetricIfAbsent(long id, ClusterMetric metric) { AssertUtil.notNull(metric, "Cluster metric cannot be null"); if (METRIC_MAP.containsKey(id)) { return false; } METRIC_MAP.put(id, metric); return true; } public static void removeMetric(long id) { METRIC_MAP.remove(id); } public static ClusterMetric getMetric(long id) { return METRIC_MAP.get(id); } public static void resetFlowMetrics() { Set keySet = METRIC_MAP.keySet(); for (Long id : keySet) { METRIC_MAP.put(id, new ClusterMetric(ClusterServerConfigManager.getSampleCount(), ClusterServerConfigManager.getIntervalMs())); } } private ClusterMetricStatistics() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterParamMetricStatistics { private static final Map METRIC_MAP = new ConcurrentHashMap<>(); public static void clear() { METRIC_MAP.clear(); } public static void putMetric(long id, ClusterParamMetric metric) { AssertUtil.notNull(metric, "metric cannot be null"); METRIC_MAP.put(id, metric); } public static boolean putMetricIfAbsent(long id, ClusterParamMetric metric) { AssertUtil.notNull(metric, "metric cannot be null"); if (METRIC_MAP.containsKey(id)) { return false; } METRIC_MAP.put(id, metric); return true; } public static void removeMetric(long id) { METRIC_MAP.remove(id); } public static ClusterParamMetric getMetric(long id) { return METRIC_MAP.get(id); } public static void resetFlowMetrics() { Set keySet = METRIC_MAP.keySet(); for (Long id : keySet) { METRIC_MAP.put(id, new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(), ClusterServerConfigManager.getIntervalMs())); } } private ClusterParamMetricStatistics() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/ClusterConcurrentCheckerLogListener.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.cluster.flow.ConcurrentClusterFlowChecker; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import java.util.Set; /** * @author yunfeiyanggzq */ public class ClusterConcurrentCheckerLogListener implements Runnable { @Override public void run() { try { collectInformation(); } catch (Exception e) { RecordLog.warn("[ClusterConcurrentCheckerLogListener] Failed to record concurrent flow control regularly", e); } } private void collectInformation() { Set keySet = CurrentConcurrencyManager.getConcurrencyMapKeySet(); for (long flowId : keySet) { FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId); if (rule == null || CurrentConcurrencyManager.get(flowId).get() == 0) { continue; } double concurrencyLevel = ConcurrentClusterFlowChecker.calcGlobalThreshold(rule); String resource = rule.getResource(); ClusterServerStatLogUtil.log(String.format("concurrent|resource:%s|flowId:%dl|concurrencyLevel:%fl|currentConcurrency", resource, flowId,concurrencyLevel),CurrentConcurrencyManager.get(flowId).get()); } if (TokenCacheNodeManager.getSize() != 0){ ClusterServerStatLogUtil.log("flow|totalTokenSize", TokenCacheNodeManager.getSize()); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * We use a ConcurrentHashMap type structure to store nowCalls corresponding to * rules, where the key is flowId and the value is nowCalls. Because nowCalls may be accessed and * modified by multiple threads, we consider to design it as an AtomicInteger class . Each newly * created rule will add a nowCalls object to this map. If the concurrency corresponding to a rule changes, * we will update the corresponding nowCalls in real time. Each request to obtain a token will increase the nowCalls; * and the request to release the token will reduce the nowCalls. * * @author yunfeiyanggzq */ public final class CurrentConcurrencyManager { /** * use ConcurrentHashMap to store the nowCalls of rules. */ private static final ConcurrentHashMap NOW_CALLS_MAP = new ConcurrentHashMap(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-cluster-concurrency-record-task", true)); static { ClusterConcurrentCheckerLogListener logTask = new ClusterConcurrentCheckerLogListener(); SCHEDULER.scheduleAtFixedRate(logTask, 0, 1, TimeUnit.SECONDS); } /** * add current concurrency. */ public static void addConcurrency(Long flowId, Integer acquireCount) { AtomicInteger nowCalls = NOW_CALLS_MAP.get(flowId); if (nowCalls == null) { return; } nowCalls.getAndAdd(acquireCount); } /** * get the current concurrency. */ public static AtomicInteger get(Long flowId) { return NOW_CALLS_MAP.get(flowId); } /** * delete the current concurrency. */ public static void remove(Long flowId) { NOW_CALLS_MAP.remove(flowId); } /** * put the current concurrency. */ public static void put(Long flowId, Integer nowCalls) { NOW_CALLS_MAP.put(flowId, new AtomicInteger(nowCalls)); } /** * check flow id. */ public static boolean containsFlowId(Long flowId) { return NOW_CALLS_MAP.containsKey(flowId); } /** * get NOW_CALLS_MAP. */ public static Set getConcurrencyMapKeySet() { return NOW_CALLS_MAP.keySet(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import java.util.UUID; /** * We use TokenCacheNodeManager to store the tokenId, whose the underlying storage structure * is ConcurrentLinkedHashMap, Its storage node is TokenCacheNode. In order to operate the nowCalls value when * the expired tokenId is deleted regularly, we need to store the flowId in TokenCacheNode. * * @author yunfeiyanggzq */ public class TokenCacheNode { /** * the TokenId of the token */ private Long tokenId; /** * the client goes offline detection time */ private Long clientTimeout; /** * the resource called over time detection time */ private Long resourceTimeout; /** * the flow rule id corresponding to the token */ private Long flowId; /** * the number this token occupied */ private int acquireCount; /** * the address of the client holds the token. */ private String clientAddress; public TokenCacheNode() { } public static TokenCacheNode generateTokenCacheNode(FlowRule rule, int acquireCount, String clientAddress) { TokenCacheNode node = new TokenCacheNode(); // getMostSignificantBits() returns the most significant 64 bits of this UUID's 128 bit value. // The probability of collision is extremely low. node.setTokenId(UUID.randomUUID().getMostSignificantBits()); node.setFlowId(rule.getClusterConfig().getFlowId()); node.setClientTimeout(rule.getClusterConfig().getClientOfflineTime()); node.setResourceTimeout(rule.getClusterConfig().getResourceTimeout()); node.setAcquireCount(acquireCount); node.setClientAddress(clientAddress); return node; } public Long getTokenId() { return tokenId; } public void setTokenId(Long tokenId) { this.tokenId = tokenId; } public Long getClientTimeout() { return clientTimeout; } public void setClientTimeout(Long clientTimeout) { this.clientTimeout = clientTimeout + System.currentTimeMillis(); } public Long getResourceTimeout() { return this.resourceTimeout; } public void setResourceTimeout(Long resourceTimeout) { this.resourceTimeout = resourceTimeout + System.currentTimeMillis(); } public Long getFlowId() { return flowId; } public void setFlowId(Long flowId) { this.flowId = flowId; } public int getAcquireCount() { return acquireCount; } public void setAcquireCount(int acquireCount) { this.acquireCount = acquireCount; } public String getClientAddress() { return clientAddress; } public void setClientAddress(String clientAddress) { this.clientAddress = clientAddress; } @Override public String toString() { return "TokenCacheNode{" + "tokenId=" + tokenId + ", clientTimeout=" + clientTimeout + ", resourceTimeout=" + resourceTimeout + ", flowId=" + flowId + ", acquireCount=" + acquireCount + ", clientAddress='" + clientAddress + '\'' + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire.RegularExpireStrategy; import com.alibaba.csp.sentinel.util.AssertUtil; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.googlecode.concurrentlinkedhashmap.Weighers; import java.util.Set; /** * @author yunfeiyanggzq */ public class TokenCacheNodeManager { private static ConcurrentLinkedHashMap TOKEN_CACHE_NODE_MAP; private static final int DEFAULT_CONCURRENCY_LEVEL = 16; private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE; static { prepare(DEFAULT_CONCURRENCY_LEVEL, DEFAULT_CAPACITY); } public static void prepare(int concurrencyLevel, int maximumWeightedCapacity) { AssertUtil.isTrue(concurrencyLevel > 0, "concurrencyLevel must be positive"); AssertUtil.isTrue(maximumWeightedCapacity > 0, "maximumWeightedCapacity must be positive"); TOKEN_CACHE_NODE_MAP = new ConcurrentLinkedHashMap.Builder() .concurrencyLevel(concurrencyLevel) .maximumWeightedCapacity(maximumWeightedCapacity) .weigher(Weighers.singleton()) .build(); // Start the task of regularly clearing expired keys RegularExpireStrategy strategy = new RegularExpireStrategy(TOKEN_CACHE_NODE_MAP); strategy.startClearTaskRegularly(); } public static TokenCacheNode getTokenCacheNode(long tokenId) { //use getQuietly to prevent disorder return TOKEN_CACHE_NODE_MAP.getQuietly(tokenId); } public static void putTokenCacheNode(long tokenId, TokenCacheNode cacheNode) { TOKEN_CACHE_NODE_MAP.put(tokenId, cacheNode); } public static boolean isContainsTokenId(long tokenId) { return TOKEN_CACHE_NODE_MAP.containsKey(tokenId); } public static TokenCacheNode removeTokenCacheNode(long tokenId) { return TOKEN_CACHE_NODE_MAP.remove(tokenId); } public static int getSize() { return TOKEN_CACHE_NODE_MAP.size(); } public static Set getCacheKeySet() { return TOKEN_CACHE_NODE_MAP.keySet(); } public static boolean validToken(TokenCacheNode cacheNode) { return cacheNode.getTokenId() != null && cacheNode.getFlowId() != null && cacheNode.getClientTimeout() >= 0 && cacheNode.getResourceTimeout() >= 0; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/ExpireStrategy.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire; /** * @author yunfeiyagnggzq */ public interface ExpireStrategy { /** * clean expired token regularly. */ void startClearTaskRegularly(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/RegularExpireStrategy.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * We need to consider the situation that the token client goes offline * or the resource call times out. It can be detected by sourceTimeout * and clientTimeout. The resource calls timeout detection is triggered * on the token client. If the resource is called over time, the token * client will request the token server to release token or refresh the * token. The client offline detection is triggered on the token server. * If the offline detection time is exceeded, token server will trigger * the detection token client’s status. If the token client is offline, * token server will delete the corresponding tokenId. If it is not offline, * token server will continue to save it. * * @author yunfeiyanggzq **/ public class RegularExpireStrategy implements ExpireStrategy { /** * The max number of token deleted each time, * the number of expired key-value pairs deleted each time does not exceed this number */ private long executeCount = 1000; /** * Length of time for task execution */ private long executeDuration = 800; /** * Frequency of task execution */ private long executeRate = 1000; /** * the local cache of tokenId */ private ConcurrentLinkedHashMap localCache; @SuppressWarnings("PMD.ThreadPoolCreationRule") private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("regular clear expired token thread", true)); public RegularExpireStrategy(ConcurrentLinkedHashMap localCache) { AssertUtil.isTrue(localCache != null, " local cache can't be null"); this.localCache = localCache; } @Override public void startClearTaskRegularly() { executor.scheduleAtFixedRate(new ClearExpiredTokenTask(), 0, executeRate, TimeUnit.MILLISECONDS); } private class ClearExpiredTokenTask implements Runnable { @Override public void run() { try { clearToken(); } catch (Throwable e) { e.printStackTrace(); RecordLog.warn("[RegularExpireStrategy] undefined throwable during clear token: ", e); } } } private void clearToken() { long start = System.currentTimeMillis(); List keyList = new ArrayList<>(localCache.keySet()); for (int i = 0; i < executeCount && i < keyList.size(); i++) { // time out execution exit if (System.currentTimeMillis() - start > executeDuration) { RecordLog.info("[RegularExpireStrategy] End the process of expired token detection because of execute time is more than executeDuration: {}", executeDuration); break; } Long key = keyList.get(i); TokenCacheNode node = localCache.get(key); if (node == null) { continue; } // remove the token whose client is offline and saved for more than clientTimeout if (!ConnectionManager.isClientOnline(node.getClientAddress()) && node.getClientTimeout() - System.currentTimeMillis() < 0) { removeToken(key, node); RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of client offline for ruleId<{}>", node.getTokenId(), node.getFlowId()); continue; } // If we find that token's save time is more than 2 times of the client's call resource timeout time, // the token will be determined to timeout. long resourceTimeout = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()).getClusterConfig().getResourceTimeout(); if (System.currentTimeMillis() - node.getResourceTimeout() > resourceTimeout) { removeToken(key, node); RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of resource timeout for ruleId<{}>", node.getTokenId(), node.getFlowId()); } } } private void removeToken(long tokenId, TokenCacheNode node) { if (localCache.remove(tokenId) == null) { RecordLog.info("[RegularExpireStrategy] Token<{}> is already released for ruleId<{}>", tokenId, node.getFlowId()); return; } AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId()); if (nowCalls == null) { return; } nowCalls.getAndAdd(node.getAcquireCount() * -1); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.data; /** * @author Eric Zhao * @since 1.4.0 */ public enum ClusterFlowEvent { /** * Normal pass. */ PASS, /** * Normal block. */ BLOCK, /** * Token request (from client) passed. */ PASS_REQUEST, /** * Token request (from client) blocked. */ BLOCK_REQUEST, /** * Pass (pre-occupy incoming buckets). */ OCCUPIED_PASS, /** * Block (pre-occupy incoming buckets failed). */ OCCUPIED_BLOCK, /** * Waiting due to flow shaping or for next bucket tick. */ WAITING } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.data; import java.util.concurrent.atomic.LongAdder; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterMetricBucket { private final LongAdder[] counters; public ClusterMetricBucket() { ClusterFlowEvent[] events = ClusterFlowEvent.values(); this.counters = new LongAdder[events.length]; for (ClusterFlowEvent event : events) { counters[event.ordinal()] = new LongAdder(); } } public void reset() { for (ClusterFlowEvent event : ClusterFlowEvent.values()) { counters[event.ordinal()].reset(); } } public long get(ClusterFlowEvent event) { return counters[event.ordinal()].sum(); } public ClusterMetricBucket add(ClusterFlowEvent event, long count) { counters[event.ordinal()].add(count); return this; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/GlobalRequestLimiter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.1 */ public final class GlobalRequestLimiter { private static final Map GLOBAL_QPS_LIMITER_MAP = new ConcurrentHashMap<>(); public static void initIfAbsent(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (!GLOBAL_QPS_LIMITER_MAP.containsKey(namespace)) { GLOBAL_QPS_LIMITER_MAP.put(namespace, new RequestLimiter(ClusterServerConfigManager.getMaxAllowedQps(namespace))); } } public static RequestLimiter getRequestLimiter(String namespace) { if (namespace == null) { return null; } return GLOBAL_QPS_LIMITER_MAP.get(namespace); } public static boolean tryPass(String namespace) { if (namespace == null) { return false; } RequestLimiter limiter = GLOBAL_QPS_LIMITER_MAP.get(namespace); if (limiter == null) { return true; } return limiter.tryPass(); } public static double getCurrentQps(String namespace) { RequestLimiter limiter = getRequestLimiter(namespace); if (limiter == null) { return 0; } return limiter.getQps(); } public static double getMaxAllowedQps(String namespace) { RequestLimiter limiter = getRequestLimiter(namespace); if (limiter == null) { return 0; } return limiter.getQpsAllowed(); } public static void applyMaxQpsChange(double maxAllowedQps) { AssertUtil.isTrue(maxAllowedQps >= 0, "max allowed QPS should > 0"); for (RequestLimiter limiter : GLOBAL_QPS_LIMITER_MAP.values()) { if (limiter != null) { limiter.setQpsAllowed(maxAllowedQps); } } } private GlobalRequestLimiter() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/RequestLimiter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; import java.util.List; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.UnaryLeapArray; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.1 */ public class RequestLimiter { private double qpsAllowed; private final LeapArray data; public RequestLimiter(double qpsAllowed) { this(new UnaryLeapArray(10, 1000), qpsAllowed); } RequestLimiter(LeapArray data, double qpsAllowed) { AssertUtil.isTrue(qpsAllowed >= 0, "max allowed QPS should > 0"); this.data = data; this.qpsAllowed = qpsAllowed; } public void increment() { data.currentWindow().value().increment(); } public void add(int x) { data.currentWindow().value().add(x); } public long getSum() { data.currentWindow(); long success = 0; List list = data.values(); for (LongAdder window : list) { success += window.sum(); } return success; } public double getQps() { return getSum() / data.getIntervalInSecond(); } public double getQpsAllowed() { return qpsAllowed; } public boolean canPass() { return getQps() + 1 <= qpsAllowed; } public RequestLimiter setQpsAllowed(double qpsAllowed) { this.qpsAllowed = qpsAllowed; return this; } public boolean tryPass() { if (canPass()) { add(1); return true; } return false; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import java.util.List; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterMetric { private final ClusterMetricLeapArray metric; public ClusterMetric(int sampleCount, int intervalInMs) { AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive"); AssertUtil.isTrue(intervalInMs > 0, "interval should be positive"); AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); this.metric = new ClusterMetricLeapArray(sampleCount, intervalInMs); } public void add(ClusterFlowEvent event, long count) { metric.currentWindow().value().add(event, count); } public long getCurrentCount(ClusterFlowEvent event) { return metric.currentWindow().value().get(event); } /** * Get total sum for provided event in {@code intervalInSec}. * * @param event event to calculate * @return total sum for event */ public long getSum(ClusterFlowEvent event) { metric.currentWindow(); long sum = 0; List buckets = metric.values(); for (ClusterMetricBucket bucket : buckets) { sum += bucket.get(event); } return sum; } /** * Get average count for provided event per second. * * @param event event to calculate * @return average count per second for event */ public double getAvg(ClusterFlowEvent event) { return getSum(event) / metric.getIntervalInSecond(); } /** * Try to pre-occupy upcoming buckets. * * @return time to wait for next bucket (in ms); 0 if cannot occupy next buckets */ public int tryOccupyNext(ClusterFlowEvent event, int acquireCount, double threshold) { double latestQps = getAvg(ClusterFlowEvent.PASS); if (!canOccupy(event, acquireCount, latestQps, threshold)) { return 0; } metric.addOccupyPass(acquireCount); add(ClusterFlowEvent.WAITING, acquireCount); return 1000 / metric.getSampleCount(); } private boolean canOccupy(ClusterFlowEvent event, int acquireCount, double latestQps, double threshold) { long headPass = metric.getFirstCountOfWindow(event); long occupiedCount = metric.getOccupiedCount(event); // bucket to occupy (= incoming bucket) // ↓ // | head bucket | | | | current bucket | // +-------------+----+----+----+----------- ----+ // (headPass) return latestQps + (acquireCount + occupiedCount) - headPass <= threshold; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterMetricLeapArray extends LeapArray { private final LongAdder[] occupyCounter; private boolean hasOccupied = false; public ClusterMetricLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); ClusterFlowEvent[] events = ClusterFlowEvent.values(); this.occupyCounter = new LongAdder[events.length]; for (ClusterFlowEvent event : events) { occupyCounter[event.ordinal()] = new LongAdder(); } } @Override public ClusterMetricBucket newEmptyBucket(long timeMillis) { return new ClusterMetricBucket(); } @Override protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { w.resetTo(startTime); w.value().reset(); transferOccupyToBucket(w.value()); return w; } private void transferOccupyToBucket(/*@Valid*/ ClusterMetricBucket bucket) { if (hasOccupied) { transferOccupiedCount(bucket, ClusterFlowEvent.PASS, ClusterFlowEvent.OCCUPIED_PASS); transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS); transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS_REQUEST); hasOccupied = false; } } private void transferOccupiedCount(ClusterMetricBucket bucket, ClusterFlowEvent source, ClusterFlowEvent target) { bucket.add(target, occupyCounter[source.ordinal()].sum()); } private void transferOccupiedThenReset(ClusterMetricBucket bucket, ClusterFlowEvent event) { bucket.add(event, occupyCounter[event.ordinal()].sumThenReset()); } public void addOccupyPass(int count) { occupyCounter[ClusterFlowEvent.PASS.ordinal()].add(count); occupyCounter[ClusterFlowEvent.PASS_REQUEST.ordinal()].add(1); this.hasOccupied = true; } public long getOccupiedCount(ClusterFlowEvent event) { return occupyCounter[event.ordinal()].sum(); } public long getFirstCountOfWindow(ClusterFlowEvent event) { if (event == null) { return 0; } WindowWrap windowWrap = getValidHead(); if (windowWrap == null) { return 0; } return windowWrap.value().get(event); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterParamMetric { public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000; private final ClusterParameterLeapArray metric; public ClusterParamMetric(int sampleCount, int intervalInMs) { this(sampleCount, intervalInMs, DEFAULT_CLUSTER_MAX_CAPACITY); } public ClusterParamMetric(int sampleCount, int intervalInMs, int maxCapacity) { AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive"); AssertUtil.isTrue(intervalInMs > 0, "interval should be positive"); AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); this.metric = new ClusterParameterLeapArray<>(sampleCount, intervalInMs, maxCapacity); } public long getSum(Object value) { if (value == null) { return 0; } metric.currentWindow(); long sum = 0; List> buckets = metric.values(); for (CacheMap bucket : buckets) { long count = getCount(bucket.get(value)); sum += count; } return sum; } private long getCount(/*@Nullable*/ LongAdder adder) { return adder == null ? 0 : adder.sum(); } public void addValue(Object value, int count) { if (value == null) { return; } CacheMap data = metric.currentWindow().value(); LongAdder newCounter = new LongAdder(); LongAdder currentCounter = data.putIfAbsent(value, newCounter); if (currentCounter != null) { currentCounter.add(count); } else { newCounter.add(count); } } public double getAvg(Object value) { return getSum(value) / metric.getIntervalInSecond(); } public Map getTopValues(int number) { AssertUtil.isTrue(number > 0, "number must be positive"); metric.currentWindow(); List> buckets = metric.values(); Map result = new HashMap<>(buckets.size()); for (CacheMap b : buckets) { Set subSet = b.keySet(true); for (Object o : subSet) { Long count = result.get(o); if (count == null) { count = getCount(b.get(o)); } else { count += getCount(b.get(o)); } result.put(o, count); } } // After merge, get the top set one. Set> set = result.entrySet(); List> list = new ArrayList<>(set); Collections.sort(list, new Comparator>() { @Override public int compare(Entry a, Entry b) { return (int) (b.getValue() == null ? 0 : b.getValue()) - (int) (a.getValue() == null ? 0 : a.getValue()); } }); Map doubleResult = new HashMap(); int size = list.size() > number ? number : list.size(); for (int i = 0; i < size; i++) { Map.Entry x = list.get(i); if (x.getValue() == 0) { break; } doubleResult.put(x.getKey(), ((double) x.getValue()) / metric.getIntervalInSecond()); } return doubleResult; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @param counter type * @author Eric Zhao * @since 1.4.0 */ public class ClusterParameterLeapArray extends LeapArray> { private final int maxCapacity; public ClusterParameterLeapArray(int sampleCount, int intervalInMs, int maxCapacity) { super(sampleCount, intervalInMs); AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive"); this.maxCapacity = maxCapacity; } @Override public CacheMap newEmptyBucket(long timeMillis) { return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); } @Override protected WindowWrap> resetWindowTo(WindowWrap> w, long startTime) { w.resetTo(startTime); w.value().clear(); return w; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import java.util.Collection; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenService; /** * Default embedded token server in Sentinel which wraps the {@link SentinelDefaultTokenServer} * and the {@link TokenService} from SPI provider. * * @author Eric Zhao * @since 1.4.0 */ public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer { private final TokenService tokenService = TokenServiceProvider.getService(); private final ClusterTokenServer server = new SentinelDefaultTokenServer(true); @Override public void start() throws Exception { server.start(); } @Override public void stop() throws Exception { server.stop(); } @Override public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) { if (tokenService != null) { return tokenService.requestToken(ruleId, acquireCount, prioritized); } return new TokenResult(TokenResultStatus.FAIL); } @Override public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params) { if (tokenService != null) { return tokenService.requestParamToken(ruleId, acquireCount, params); } return new TokenResult(TokenResultStatus.FAIL); } @Override public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) { return null; } @Override public void releaseConcurrentToken(Long tokenId) { } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyRequestDecoder; import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyResponseEncoder; import com.alibaba.csp.sentinel.cluster.server.connection.Connection; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool; import com.alibaba.csp.sentinel.cluster.server.handler.TokenServerHandler; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.internal.SystemPropertyUtil; import static com.alibaba.csp.sentinel.cluster.server.ServerConstants.*; /** * @author Eric Zhao * @since 1.4.0 */ public class NettyTransportServer implements ClusterTokenServer { private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); private static final int MAX_RETRY_TIMES = 3; private static final int RETRY_SLEEP_MS = 2000; private final int port; private NioEventLoopGroup bossGroup; private NioEventLoopGroup workerGroup; private final ConnectionPool connectionPool = new ConnectionPool(); private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF); private final AtomicInteger failedTimes = new AtomicInteger(0); public NettyTransportServer(int port) { this.port = port; } @Override public void start() { if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) { return; } ServerBootstrap b = new ServerBootstrap(); this.bossGroup = new NioEventLoopGroup(1); this.workerGroup = new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); p.addLast(new NettyRequestDecoder()); p.addLast(new LengthFieldPrepender(2)); p.addLast(new NettyResponseEncoder()); p.addLast(new TokenServerHandler(connectionPool)); } }) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.SO_SNDBUF, 32 * 1024) .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .childOption(ChannelOption.SO_TIMEOUT, 10) .childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.SO_RCVBUF, 32 * 1024); b.bind(port).addListener(new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.cause() != null) { RecordLog.info("[NettyTransportServer] Token server start failed (port=" + port + "), failedTimes: " + failedTimes.get(), future.cause()); currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF); int failCount = failedTimes.incrementAndGet(); if (failCount > MAX_RETRY_TIMES) { return; } try { Thread.sleep(failCount * RETRY_SLEEP_MS); start(); } catch (Throwable e) { RecordLog.info("[NettyTransportServer] Failed to start token server when retrying", e); } } else { RecordLog.info("[NettyTransportServer] Token server started success at port {}", port); currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED); } } }); } @Override public void stop() { // If still initializing, wait for ready. while (currentState.get() == SERVER_STATUS_STARTING) { try { Thread.sleep(500); } catch (InterruptedException e) { // Ignore. } } if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) { try { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); connectionPool.shutdownAll(); failedTimes.set(0); RecordLog.info("[NettyTransportServer] Sentinel token server stopped"); } catch (Exception ex) { RecordLog.warn("[NettyTransportServer] Failed to stop token server (port=" + port + ")", ex); } } } public void refreshRunningServer() { connectionPool.refreshIdleTask(); } public void closeConnection(String clientIp, int clientPort) throws Exception { Connection connection = connectionPool.getConnection(clientIp, clientPort); connection.close(); } public void closeAll() throws Exception { List connections = connectionPool.listAllConnection(); for (Connection connection : connections) { connection.close(); } } public List listAllClient() { List clients = new ArrayList(); List connections = connectionPool.listAllConnection(); for (Connection conn : connections) { clients.add(conn.getConnectionKey()); } return clients; } public int getCurrentState() { return currentState.get(); } public int clientCount() { return connectionPool.count(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfigObserver; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.4.0 */ public class SentinelDefaultTokenServer implements ClusterTokenServer { private final boolean embedded; private ClusterTokenServer server; private int port; private final AtomicBoolean shouldStart = new AtomicBoolean(false); static { InitExecutor.doInit(); } public SentinelDefaultTokenServer() { this(false); } public SentinelDefaultTokenServer(boolean embedded) { this.embedded = embedded; ClusterServerConfigManager.addTransportConfigChangeObserver(new ServerTransportConfigObserver() { @Override public void onTransportConfigChange(ServerTransportConfig config) { changeServerConfig(config); } }); initNewServer(); } private void initNewServer() { if (server != null) { return; } int port = ClusterServerConfigManager.getPort(); if (port > 0) { this.server = new NettyTransportServer(port); this.port = port; } } private synchronized void changeServerConfig(ServerTransportConfig config) { if (config == null || config.getPort() <= 0) { return; } int newPort = config.getPort(); if (newPort == port) { return; } try { if (server != null) { stopServer(); } this.server = new NettyTransportServer(newPort); this.port = newPort; startServerIfScheduled(); } catch (Exception ex) { RecordLog.warn("[SentinelDefaultTokenServer] Failed to apply modification to token server", ex); } } private void startServerIfScheduled() throws Exception { if (shouldStart.get()) { if (server != null) { server.start(); ClusterStateManager.markToServer(); if (embedded) { RecordLog.info("[SentinelDefaultTokenServer] Running in embedded mode"); handleEmbeddedStart(); } } } } private void stopServer() throws Exception { if (server != null) { server.stop(); if (embedded) { handleEmbeddedStop(); } } } private void handleEmbeddedStop() { String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get(); if (StringUtil.isNotEmpty(namespace)) { ConnectionManager.removeConnection(namespace, HostNameUtil.getIp()); } } private void handleEmbeddedStart() { String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get(); if (StringUtil.isNotEmpty(namespace)) { // Mark server global mode as embedded. ClusterServerConfigManager.setEmbedded(true); if (!ClusterServerConfigManager.getNamespaceSet().contains(namespace)) { Set namespaceSet = new HashSet<>(ClusterServerConfigManager.getNamespaceSet()); namespaceSet.add(namespace); ClusterServerConfigManager.loadServerNamespaceSet(namespaceSet); } // Register self to connection group. ConnectionManager.addConnection(namespace, HostNameUtil.getIp()); } } @Override public void start() throws Exception { if (shouldStart.compareAndSet(false, true)) { startServerIfScheduled(); } } @Override public void stop() throws Exception { if (shouldStart.compareAndSet(true, false)) { stopServer(); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; /** * @author Eric Zhao * @since 1.4.0 */ public final class ServerConstants { public static final int SERVER_STATUS_OFF = 0; public static final int SERVER_STATUS_STARTING = 1; public static final int SERVER_STATUS_STARTED = 2; public static final String DEFAULT_NAMESPACE = "default"; private ServerConstants() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * @author Eric Zhao * @since 1.4.0 */ public final class TokenServiceProvider { private static TokenService service = null; static { resolveTokenServiceSpi(); } public static TokenService getService() { return service; } private static void resolveTokenServiceSpi() { service = SpiLoader.of(TokenService.class).loadFirstInstanceOrDefault(); if (service != null) { RecordLog.info("[TokenServiceProvider] Global token service resolved: " + service.getClass().getCanonicalName()); } else { RecordLog.warn("[TokenServiceProvider] Unable to resolve TokenService: no SPI found"); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; /** *

Default entity decoder for any {@link ClusterRequest} entity.

* *

Decode format:

*
 * +--------+---------+---------+
 * | xid(4) | type(1) | data... |
 * +--------+---------+---------+
 * 
* * @author Eric Zhao * @since 1.4.0 */ public class DefaultRequestEntityDecoder implements RequestEntityDecoder { @Override public ClusterRequest decode(ByteBuf source) { if (source.readableBytes() >= 5) { int xid = source.readInt(); int type = source.readByte(); EntityDecoder dataDecoder = RequestDataDecodeRegistry.getDecoder(type); if (dataDecoder == null) { RecordLog.warn("Unknown type of request data decoder: {}", type); return null; } Object data; if (source.readableBytes() == 0) { data = null; } else { data = dataDecoder.decode(source); } return new ClusterRequest<>(xid, type, data); } return null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.Response; import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class DefaultResponseEntityWriter implements ResponseEntityWriter { @Override public void writeTo(ClusterResponse response, ByteBuf out) { int type = response.getType(); EntityWriter responseDataWriter = ResponseDataWriterRegistry.getWriter(type); if (responseDataWriter == null) { writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out); RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{}>", response.getType()); return; } writeHead(response, out); responseDataWriter.writeTo(response.getData(), out); } private void writeHead(Response response, ByteBuf out) { out.writeInt(response.getId()); out.writeByte(response.getType()); out.writeByte(response.getStatus()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * @author Eric Zhao * @since 1.4.0 */ public final class ServerEntityCodecProvider { private static RequestEntityDecoder requestEntityDecoder = null; private static ResponseEntityWriter responseEntityWriter = null; static { resolveInstance(); } private static void resolveInstance() { ResponseEntityWriter writer = SpiLoader.of(ResponseEntityWriter.class).loadFirstInstance(); if (writer == null) { RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed"); } else { responseEntityWriter = writer; RecordLog.info("[ServerEntityCodecProvider] Response entity writer resolved: {}", responseEntityWriter.getClass().getCanonicalName()); } RequestEntityDecoder decoder = SpiLoader.of(RequestEntityDecoder.class).loadFirstInstance(); if (decoder == null) { RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed"); } else { requestEntityDecoder = decoder; RecordLog.info("[ServerEntityCodecProvider] Request entity decoder resolved: {}", requestEntityDecoder.getClass().getCanonicalName()); } } public static RequestEntityDecoder getRequestEntityDecoder() { return requestEntityDecoder; } public static ResponseEntityWriter getResponseEntityWriter() { return responseEntityWriter; } private ServerEntityCodecProvider() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import io.netty.buffer.ByteBuf; /** *

* Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout: *

*
 * | flow ID (8) | count (4) | priority flag (1) |
 * 
* * @author Eric Zhao * @since 1.4.0 */ public class FlowRequestDataDecoder implements EntityDecoder { @Override public FlowRequestData decode(ByteBuf source) { if (source.readableBytes() >= 12) { FlowRequestData requestData = new FlowRequestData() .setFlowId(source.readLong()) .setCount(source.readInt()); if (source.readableBytes() >= 1) { requestData.setPriority(source.readBoolean()); } return requestData; } return null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class FlowResponseDataWriter implements EntityWriter { @Override public void writeTo(FlowTokenResponseData entity, ByteBuf out) { out.writeInt(entity.getRemainingCount()); out.writeInt(entity.getWaitInMs()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; import io.netty.buffer.ByteBuf; /** * @author jialiang.linjl * @author Eric Zhao * @since 1.4.0 */ public class ParamFlowRequestDataDecoder implements EntityDecoder { @Override public ParamFlowRequestData decode(ByteBuf source) { if (source.readableBytes() >= 16) { ParamFlowRequestData requestData = new ParamFlowRequestData() .setFlowId(source.readLong()) .setCount(source.readInt()); int amount = source.readInt(); if (amount > 0) { List params = new ArrayList<>(amount); for (int i = 0; i < amount; i++) { decodeParam(source, params); } requestData.setParams(params); return requestData; } } return null; } private boolean decodeParam(ByteBuf source, List params) { byte paramType = source.readByte(); switch (paramType) { case ClusterConstants.PARAM_TYPE_INTEGER: params.add(source.readInt()); return true; case ClusterConstants.PARAM_TYPE_STRING: int length = source.readInt(); byte[] bytes = new byte[length]; source.readBytes(bytes); // TODO: take care of charset? params.add(new String(bytes)); return true; case ClusterConstants.PARAM_TYPE_BOOLEAN: params.add(source.readBoolean()); return true; case ClusterConstants.PARAM_TYPE_DOUBLE: params.add(source.readDouble()); return true; case ClusterConstants.PARAM_TYPE_LONG: params.add(source.readLong()); return true; case ClusterConstants.PARAM_TYPE_FLOAT: params.add(source.readFloat()); return true; case ClusterConstants.PARAM_TYPE_BYTE: params.add(source.readByte()); return true; case ClusterConstants.PARAM_TYPE_SHORT: params.add(source.readShort()); return true; default: return false; } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingRequestDataDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class PingRequestDataDecoder implements EntityDecoder { @Override public String decode(ByteBuf source) { if (source.readableBytes() >= 4) { int length = source.readInt(); if (length > 0 && source.readableBytes() > 0) { byte[] bytes = new byte[length]; source.readBytes(bytes); return new String(bytes); } } return null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public class PingResponseDataWriter implements EntityWriter { @Override public void writeTo(Integer entity, ByteBuf target) { if (entity == null || target == null) { return; } target.writeInt(entity); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.netty; import java.util.List; import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder; import com.alibaba.csp.sentinel.cluster.request.Request; import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; /** * @author Eric Zhao * @since 1.4.0 */ public class NettyRequestDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { RequestEntityDecoder requestDecoder = ServerEntityCodecProvider.getRequestEntityDecoder(); if (requestDecoder == null) { RecordLog.warn("[NettyRequestDecoder] Cannot resolve the global request entity decoder, " + "dropping the request"); return; } // TODO: handle decode error here. Request request = requestDecoder.decode(in); if (request != null) { out.add(request); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.netty; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.Response; import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider; import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * @author Eric Zhao * @since 1.4.0 */ public class NettyResponseEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception { ResponseEntityWriter responseEntityWriter = ServerEntityCodecProvider.getResponseEntityWriter(); if (responseEntityWriter == null) { RecordLog.warn("[NettyResponseEncoder] Cannot resolve the global response entity writer, reply bad status"); writeBadStatusHead(response, out); return; } responseEntityWriter.writeTo(response, out); } private void writeBadStatusHead(Response response, ByteBuf out) { out.writeInt(response.getId()); out.writeByte(ClusterConstants.RESPONSE_STATUS_BAD); out.writeByte(response.getStatus()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.registry; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public final class RequestDataDecodeRegistry { private static final Map> DECODER_MAP = new HashMap<>(); public static boolean addDecoder(int type, EntityDecoder decoder) { if (DECODER_MAP.containsKey(type)) { return false; } DECODER_MAP.put(type, decoder); return true; } public static EntityDecoder getDecoder(int type) { return (EntityDecoder)DECODER_MAP.get(type); } public static boolean removeDecoder(int type) { return DECODER_MAP.remove(type) != null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.registry; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; import io.netty.buffer.ByteBuf; /** * @author Eric Zhao * @since 1.4.0 */ public final class ResponseDataWriterRegistry { private static final Map> WRITER_MAP = new HashMap<>(); public static boolean addWriter(int type, EntityWriter writer) { if (WRITER_MAP.containsKey(type)) { return false; } WRITER_MAP.put(type, (EntityWriter)writer); return true; } public static EntityWriter getWriter(int type) { return WRITER_MAP.get(type); } public static boolean remove(int type) { return WRITER_MAP.remove(type) != null; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/flowRules", desc = "get cluster flow rules") public class FetchClusterFlowRulesCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getAllFlowRules())); } else { return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getFlowRules(namespace))); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterMetricCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricNodeGenerator; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.1 */ @CommandMapping(name = "cluster/server/metricList", desc = "get cluster server metrics") public class FetchClusterMetricCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return CommandResponse.ofFailure(new IllegalArgumentException("failed: namespace cannot be empty")); } return CommandResponse.ofSuccess( JSON.toJSONString(ClusterMetricNodeGenerator.generateCurrentNodeMap(namespace)) ); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/paramRules", desc = "get cluster server param flow rules") public class FetchClusterParamFlowRulesCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getAllParamRules())); } else { return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getParamRules(namespace))); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONObject; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/fetchConfig", desc = "get cluster server config") public class FetchClusterServerConfigHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return globalConfigResult(); } return namespaceConfigResult(namespace); } private CommandResponse namespaceConfigResult(/*@NonEmpty*/ String namespace) { ServerFlowConfig flowConfig = new ServerFlowConfig() .setExceedCount(ClusterServerConfigManager.getExceedCount(namespace)) .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio(namespace)) .setIntervalMs(ClusterServerConfigManager.getIntervalMs(namespace)) .setSampleCount(ClusterServerConfigManager.getSampleCount(namespace)); JSONObject config = new JSONObject() .fluentPut("flow", flowConfig); return CommandResponse.ofSuccess(config.toJSONString()); } private CommandResponse globalConfigResult() { ServerTransportConfig transportConfig = new ServerTransportConfig() .setPort(ClusterServerConfigManager.getPort()) .setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); ServerFlowConfig flowConfig = new ServerFlowConfig() .setExceedCount(ClusterServerConfigManager.getExceedCount()) .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) .setIntervalMs(ClusterServerConfigManager.getIntervalMs()) .setSampleCount(ClusterServerConfigManager.getSampleCount()); JSONObject config = new JSONObject() .fluentPut("transport", transportConfig) .fluentPut("flow", flowConfig) .fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet()); return CommandResponse.ofSuccess(config.toJSONString()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import java.util.Set; import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionGroup; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/info", desc = "get cluster server info") public class FetchClusterServerInfoCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { JSONObject info = new JSONObject(); JSONArray connectionGroups = new JSONArray(); Set namespaceSet = ClusterServerConfigManager.getNamespaceSet(); for (String namespace : namespaceSet) { ConnectionGroup group = ConnectionManager.getOrCreateConnectionGroup(namespace); if (group != null) { connectionGroups.add(group); } } ServerTransportConfig transportConfig = new ServerTransportConfig() .setPort(ClusterServerConfigManager.getPort()) .setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); ServerFlowConfig flowConfig = new ServerFlowConfig() .setExceedCount(ClusterServerConfigManager.getExceedCount()) .setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) .setIntervalMs(ClusterServerConfigManager.getIntervalMs()) .setSampleCount(ClusterServerConfigManager.getSampleCount()) .setMaxAllowedQps(ClusterServerConfigManager.getMaxAllowedQps()); JSONArray requestLimitData = buildRequestLimitData(namespaceSet); info.fluentPut("port", ClusterServerConfigManager.getPort()) .fluentPut("connection", connectionGroups) .fluentPut("requestLimitData", requestLimitData) .fluentPut("transport", transportConfig) .fluentPut("flow", flowConfig) .fluentPut("namespaceSet", namespaceSet) .fluentPut("embedded", ClusterServerConfigManager.isEmbedded()); // Since 1.5.0 the appName is carried so that the caller can identify the appName of the token server. info.put("appName", AppNameUtil.getAppName()); return CommandResponse.ofSuccess(info.toJSONString()); } private JSONArray buildRequestLimitData(Set namespaceSet) { JSONArray array = new JSONArray(); for (String namespace : namespaceSet) { array.add(new JSONObject() .fluentPut("namespace", namespace) .fluentPut("currentQps", GlobalRequestLimiter.getCurrentQps(namespace)) .fluentPut("maxAllowedQps", GlobalRequestLimiter.getMaxAllowedQps(namespace)) ); } return array; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import java.net.URLDecoder; import java.util.List; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONArray; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/modifyFlowRules", desc = "modify cluster flow rules") public class ModifyClusterFlowRulesCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); } String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); } try { data = URLDecoder.decode(data, "UTF-8"); RecordLog.info("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{}>: {}", namespace, data); List flowRules = JSONArray.parseArray(data, FlowRule.class); ClusterFlowRuleManager.loadRules(namespace, flowRules); return CommandResponse.ofSuccess(SUCCESS); } catch (Exception e) { RecordLog.warn("[ModifyClusterFlowRulesCommandHandler] Decode cluster flow rules error", e); return CommandResponse.ofFailure(e, "decode cluster flow rules error"); } } private static final String SUCCESS = "success"; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import java.net.URLDecoder; import java.util.List; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONArray; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/modifyParamRules", desc = "modify cluster param flow rules") public class ModifyClusterParamFlowRulesCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String namespace = request.getParam("namespace"); if (StringUtil.isEmpty(namespace)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); } String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); } try { data = URLDecoder.decode(data, "UTF-8"); RecordLog.info("Receiving cluster param rules for namespace <{}> from command handler: {}", namespace, data); List flowRules = JSONArray.parseArray(data, ParamFlowRule.class); ClusterParamFlowRuleManager.loadRules(namespace, flowRules); return CommandResponse.ofSuccess(SUCCESS); } catch (Exception e) { RecordLog.warn("[ModifyClusterParamFlowRulesCommandHandler] Decode cluster param rules error", e); return CommandResponse.ofFailure(e, "decode cluster param rules error"); } } private static final String SUCCESS = "success"; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import java.net.URLDecoder; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/modifyFlowConfig", desc = "modify cluster server flow config") public class ModifyClusterServerFlowConfigHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); } String namespace = request.getParam("namespace"); try { data = URLDecoder.decode(data, "utf-8"); if (StringUtil.isEmpty(namespace)) { RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: {}", data); ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); if (!ClusterServerConfigManager.isValidFlowConfig(config)) { CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config")); } ClusterServerConfigManager.loadGlobalFlowConfig(config); } else { RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{}>: {}", namespace, data); ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); if (!ClusterServerConfigManager.isValidFlowConfig(config)) { CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config")); } ClusterServerConfigManager.loadFlowConfig(namespace, config); } return CommandResponse.ofSuccess("success"); } catch (Exception e) { RecordLog.warn("[ModifyClusterServerFlowConfigHandler] Decode cluster server flow config error", e); return CommandResponse.ofFailure(e, "decode cluster server flow config error"); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/modifyTransportConfig", desc = "modify cluster server transport config") public class ModifyClusterServerTransportConfigHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String portValue = request.getParam("port"); if (StringUtil.isBlank(portValue)) { return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty port")); } String idleSecondsValue = request.getParam("idleSeconds"); if (StringUtil.isBlank(idleSecondsValue)) { return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty idleSeconds")); } try { int port = Integer.valueOf(portValue); int idleSeconds = Integer.valueOf(idleSecondsValue); ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() .setPort(port).setIdleSeconds(idleSeconds)); return CommandResponse.ofSuccess("success"); } catch (NumberFormatException e) { return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter")); } catch (Exception ex) { return CommandResponse.ofFailure(new IllegalArgumentException("unexpected error")); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.command.handler; import java.net.URLDecoder; import java.util.Set; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "cluster/server/modifyNamespaceSet", desc = "modify server namespace set") public class ModifyServerNamespaceSetHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); } try { data = URLDecoder.decode(data, "utf-8"); RecordLog.info("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: {}", data); Set set = JSON.parseObject(data, new TypeReference>() {}); ClusterServerConfigManager.loadServerNamespaceSet(set); return CommandResponse.ofSuccess("success"); } catch (Exception e) { RecordLog.warn("[ModifyServerNamespaceSetHandler] Decode cluster server namespace set error", e); return CommandResponse.ofFailure(e, "decode client cluster config error"); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.config; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter; import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterServerConfigManager { private static boolean embedded = false; /** * Server global transport and scope config. */ private static volatile int port = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT; private static volatile int idleSeconds = ServerTransportConfig.DEFAULT_IDLE_SECONDS; private static volatile Set namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); /** * Server global flow config. */ private static volatile double exceedCount = ServerFlowConfig.DEFAULT_EXCEED_COUNT; private static volatile double maxOccupyRatio = ServerFlowConfig.DEFAULT_MAX_OCCUPY_RATIO; private static volatile int intervalMs = ServerFlowConfig.DEFAULT_INTERVAL_MS; private static volatile int sampleCount = ServerFlowConfig.DEFAULT_SAMPLE_COUNT; private static volatile double maxAllowedQps = ServerFlowConfig.DEFAULT_MAX_ALLOWED_QPS; /** * Namespace-specific flow config for token server. * Format: (namespace, config). */ private static final Map NAMESPACE_CONF = new ConcurrentHashMap<>(); private static final List TRANSPORT_CONFIG_OBSERVERS = new ArrayList<>(); /** * Property for cluster server global transport configuration. */ private static SentinelProperty transportConfigProperty = new DynamicSentinelProperty<>(); /** * Property for cluster server namespace set. */ private static SentinelProperty> namespaceSetProperty = new DynamicSentinelProperty<>(); /** * Property for cluster server global flow control configuration. */ private static SentinelProperty globalFlowProperty = new DynamicSentinelProperty<>(); private static final PropertyListener TRANSPORT_PROPERTY_LISTENER = new ServerGlobalTransportPropertyListener(); private static final PropertyListener GLOBAL_FLOW_PROPERTY_LISTENER = new ServerGlobalFlowPropertyListener(); private static final PropertyListener> NAMESPACE_SET_PROPERTY_LISTENER = new ServerNamespaceSetPropertyListener(); static { transportConfigProperty.addListener(TRANSPORT_PROPERTY_LISTENER); globalFlowProperty.addListener(GLOBAL_FLOW_PROPERTY_LISTENER); namespaceSetProperty.addListener(NAMESPACE_SET_PROPERTY_LISTENER); } /** * Register cluster server namespace set dynamic property. * * @param property server namespace set dynamic property */ public static void registerNamespaceSetProperty(SentinelProperty> property) { AssertUtil.notNull(property, "namespace set dynamic property cannot be null"); synchronized (NAMESPACE_SET_PROPERTY_LISTENER) { RecordLog.info( "[ClusterServerConfigManager] Registering new namespace set dynamic property to Sentinel server " + "config manager"); namespaceSetProperty.removeListener(NAMESPACE_SET_PROPERTY_LISTENER); property.addListener(NAMESPACE_SET_PROPERTY_LISTENER); namespaceSetProperty = property; } } /** * Register cluster server transport configuration dynamic property. * * @param property server transport configuration dynamic property */ public static void registerServerTransportProperty(SentinelProperty property) { AssertUtil.notNull(property, "cluster server transport config dynamic property cannot be null"); synchronized (TRANSPORT_PROPERTY_LISTENER) { RecordLog.info( "[ClusterServerConfigManager] Registering new server transport dynamic property to Sentinel server " + "config manager"); transportConfigProperty.removeListener(TRANSPORT_PROPERTY_LISTENER); property.addListener(TRANSPORT_PROPERTY_LISTENER); transportConfigProperty = property; } } /** * Register cluster server global statistic (flow) configuration dynamic property. * * @param property server flow configuration dynamic property */ public static void registerGlobalServerFlowProperty(SentinelProperty property) { AssertUtil.notNull(property, "cluster server flow config dynamic property cannot be null"); synchronized (GLOBAL_FLOW_PROPERTY_LISTENER) { RecordLog.info( "[ClusterServerConfigManager] Registering new server global flow dynamic property " + "to Sentinel server config manager"); globalFlowProperty.removeListener(GLOBAL_FLOW_PROPERTY_LISTENER); property.addListener(GLOBAL_FLOW_PROPERTY_LISTENER); globalFlowProperty = property; } } /** * Load provided server namespace set to property in memory. * * @param namespaceSet valid namespace set */ public static void loadServerNamespaceSet(Set namespaceSet) { namespaceSetProperty.updateValue(namespaceSet); } /** * Load provided server transport configuration to property in memory. * * @param config valid cluster server transport configuration */ public static void loadGlobalTransportConfig(ServerTransportConfig config) { transportConfigProperty.updateValue(config); } /** * Load provided server global statistic (flow) configuration to property in memory. * * @param config valid cluster server flow configuration for global */ public static void loadGlobalFlowConfig(ServerFlowConfig config) { globalFlowProperty.updateValue(config); } /** * Load server flow config for a specific namespace. * * @param namespace a valid namespace * @param config valid flow config for the namespace */ public static void loadFlowConfig(String namespace, ServerFlowConfig config) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); // TODO: Support namespace-scope server flow config. globalFlowProperty.updateValue(config); } /** * Add a transport config observer. The observers will be called as soon as * there are some changes in transport config (e.g. token server port). * * @param observer a valid transport config observer */ public static void addTransportConfigChangeObserver(ServerTransportConfigObserver observer) { AssertUtil.notNull(observer, "observer cannot be null"); TRANSPORT_CONFIG_OBSERVERS.add(observer); } private static class ServerNamespaceSetPropertyListener implements PropertyListener> { @Override public synchronized void configLoad(Set set) { if (set == null || set.isEmpty()) { RecordLog.warn("[ClusterServerConfigManager] WARN: empty initial server namespace set"); return; } applyNamespaceSetChange(set); } @Override public synchronized void configUpdate(Set set) { // TODO: should debounce? applyNamespaceSetChange(set); } } private static void applyNamespaceSetChange(Set newSet) { if (newSet == null) { return; } RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: {}", newSet); if (newSet.isEmpty()) { ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); return; } newSet = new HashSet<>(newSet); // Always add the `default` namespace to the namespace set. newSet.add(ServerConstants.DEFAULT_NAMESPACE); if (embedded) { // In embedded server mode, the server itself is also a part of service, // so it should be added to namespace set. // By default, the added namespace is the appName. newSet.add(ConfigSupplierRegistry.getNamespaceSupplier().get()); } Set oldSet = ClusterServerConfigManager.namespaceSet; if (oldSet != null && !oldSet.isEmpty()) { for (String ns : oldSet) { // Remove the cluster rule property for deprecated namespace set. if (!newSet.contains(ns)) { ClusterFlowRuleManager.removeProperty(ns); ClusterParamFlowRuleManager.removeProperty(ns); } } } ClusterServerConfigManager.namespaceSet = newSet; for (String ns : newSet) { // Register the rule property if needed. ClusterFlowRuleManager.registerPropertyIfAbsent(ns); ClusterParamFlowRuleManager.registerPropertyIfAbsent(ns); // Initialize the global QPS limiter for the namespace. GlobalRequestLimiter.initIfAbsent(ns); } } private static class ServerGlobalTransportPropertyListener implements PropertyListener { @Override public void configLoad(ServerTransportConfig config) { if (config == null) { RecordLog.warn("[ClusterServerConfigManager] Empty initial server transport config"); return; } applyConfig(config); } @Override public void configUpdate(ServerTransportConfig config) { applyConfig(config); } private synchronized void applyConfig(ServerTransportConfig config) { if (!isValidTransportConfig(config)) { RecordLog.warn( "[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: {}", config); return; } RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: {}", config); if (config.getIdleSeconds() != idleSeconds) { idleSeconds = config.getIdleSeconds(); } updateTokenServer(config); } } private static void updateTokenServer(ServerTransportConfig config) { int newPort = config.getPort(); AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)"); if (newPort == port) { return; } ClusterServerConfigManager.port = newPort; for (ServerTransportConfigObserver observer : TRANSPORT_CONFIG_OBSERVERS) { observer.onTransportConfigChange(config); } } private static class ServerGlobalFlowPropertyListener implements PropertyListener { @Override public void configUpdate(ServerFlowConfig config) { applyGlobalFlowConfig(config); } @Override public void configLoad(ServerFlowConfig config) { applyGlobalFlowConfig(config); } private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { if (!isValidFlowConfig(config)) { RecordLog.warn( "[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: {}", config); return; } RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: {}", config); if (config.getExceedCount() != exceedCount) { exceedCount = config.getExceedCount(); } if (config.getMaxOccupyRatio() != maxOccupyRatio) { maxOccupyRatio = config.getMaxOccupyRatio(); } if (config.getMaxAllowedQps() != maxAllowedQps) { maxAllowedQps = config.getMaxAllowedQps(); GlobalRequestLimiter.applyMaxQpsChange(maxAllowedQps); } int newIntervalMs = config.getIntervalMs(); int newSampleCount = config.getSampleCount(); if (newIntervalMs != intervalMs || newSampleCount != sampleCount) { if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) { RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count"); } else { intervalMs = newIntervalMs; sampleCount = newSampleCount; // Reset all the metrics. ClusterMetricStatistics.resetFlowMetrics(); ClusterParamMetricStatistics.resetFlowMetrics(); } } } } public static boolean isValidTransportConfig(ServerTransportConfig config) { return config != null && config.getPort() > 0 && config.getPort() <= 65535; } public static boolean isValidFlowConfig(ServerFlowConfig config) { return config != null && config.getMaxOccupyRatio() >= 0 && config.getExceedCount() >= 0 && config.getMaxAllowedQps() >= 0 && FlowRuleUtil.isWindowConfigValid(config.getSampleCount(), config.getIntervalMs()); } public static double getExceedCount(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); if (config != null) { return config.getExceedCount(); } return exceedCount; } public static double getMaxOccupyRatio(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); if (config != null) { return config.getMaxOccupyRatio(); } return maxOccupyRatio; } public static int getIntervalMs(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); if (config != null) { return config.getIntervalMs(); } return intervalMs; } /** * Get sample count of provided namespace. * * @param namespace valid namespace * @return the sample count of namespace; if the namespace does not have customized value, use the global value */ public static int getSampleCount(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); if (config != null) { return config.getSampleCount(); } return sampleCount; } public static double getMaxAllowedQps() { return maxAllowedQps; } public static double getMaxAllowedQps(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); if (config != null) { return config.getMaxAllowedQps(); } return maxAllowedQps; } public static double getExceedCount() { return exceedCount; } public static double getMaxOccupyRatio() { return maxOccupyRatio; } public static Set getNamespaceSet() { return namespaceSet; } public static int getPort() { return port; } public static int getIdleSeconds() { return idleSeconds; } public static int getIntervalMs() { return intervalMs; } public static int getSampleCount() { return sampleCount; } public static void setNamespaceSet(Set namespaceSet) { applyNamespaceSetChange(namespaceSet); } public static boolean isEmbedded() { return embedded; } /** *

Set the embedded mode flag for the token server.

*

* NOTE: developers SHOULD NOT manually invoke this method. * The embedded flag should be initialized by Sentinel when starting token server. *

* * @param embedded whether the token server is currently running in embedded mode */ public static void setEmbedded(boolean embedded) { ClusterServerConfigManager.embedded = embedded; } public static void setMaxAllowedQps(double maxAllowedQps) { ClusterServerConfigManager.maxAllowedQps = maxAllowedQps; } private ClusterServerConfigManager() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.config; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; /** * @author Eric Zhao * @since 1.4.0 */ public class ServerFlowConfig { public static final double DEFAULT_EXCEED_COUNT = 1.0d; public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d; public static final int DEFAULT_INTERVAL_MS = 1000; public static final int DEFAULT_SAMPLE_COUNT= 10; public static final double DEFAULT_MAX_ALLOWED_QPS= 30000; private final String namespace; private double exceedCount = DEFAULT_EXCEED_COUNT; private double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO; private int intervalMs = DEFAULT_INTERVAL_MS; private int sampleCount = DEFAULT_SAMPLE_COUNT; private double maxAllowedQps = DEFAULT_MAX_ALLOWED_QPS; public ServerFlowConfig() { this(ServerConstants.DEFAULT_NAMESPACE); } public ServerFlowConfig(String namespace) { this.namespace = namespace; } public String getNamespace() { return namespace; } public double getExceedCount() { return exceedCount; } public ServerFlowConfig setExceedCount(double exceedCount) { this.exceedCount = exceedCount; return this; } public double getMaxOccupyRatio() { return maxOccupyRatio; } public ServerFlowConfig setMaxOccupyRatio(double maxOccupyRatio) { this.maxOccupyRatio = maxOccupyRatio; return this; } public int getIntervalMs() { return intervalMs; } public ServerFlowConfig setIntervalMs(int intervalMs) { this.intervalMs = intervalMs; return this; } public int getSampleCount() { return sampleCount; } public ServerFlowConfig setSampleCount(int sampleCount) { this.sampleCount = sampleCount; return this; } public double getMaxAllowedQps() { return maxAllowedQps; } public ServerFlowConfig setMaxAllowedQps(double maxAllowedQps) { this.maxAllowedQps = maxAllowedQps; return this; } @Override public String toString() { return "ServerFlowConfig{" + "namespace='" + namespace + '\'' + ", exceedCount=" + exceedCount + ", maxOccupyRatio=" + maxOccupyRatio + ", intervalMs=" + intervalMs + ", sampleCount=" + sampleCount + ", maxAllowedQps=" + maxAllowedQps + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.config; import com.alibaba.csp.sentinel.cluster.ClusterConstants; /** * @author Eric Zhao * @since 1.4.0 */ public class ServerTransportConfig { public static final int DEFAULT_IDLE_SECONDS = 600; private int port; private int idleSeconds; public ServerTransportConfig() { this(ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT, DEFAULT_IDLE_SECONDS); } public ServerTransportConfig(int port, int idleSeconds) { this.port = port; this.idleSeconds = idleSeconds; } public int getPort() { return port; } public ServerTransportConfig setPort(int port) { this.port = port; return this; } public int getIdleSeconds() { return idleSeconds; } public ServerTransportConfig setIdleSeconds(int idleSeconds) { this.idleSeconds = idleSeconds; return this; } @Override public String toString() { return "ServerTransportConfig{" + "port=" + port + ", idleSeconds=" + idleSeconds + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.config; /** * @author Eric Zhao * @since 1.4.0 */ public interface ServerTransportConfigObserver { /** * Callback on server transport config (e.g. port) change. * * @param config new server transport config */ void onTransportConfigChange(ServerTransportConfig config); } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.net.SocketAddress; /** * @author xuyue * @author Eric Zhao * @since 1.4.0 */ public interface Connection extends AutoCloseable { SocketAddress getLocalAddress(); int getRemotePort(); String getRemoteIP(); void refreshLastReadTime(long lastReadTime); long getLastReadTime(); String getConnectionKey(); } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.Objects; /** * @author Eric Zhao * @since 1.4.0 */ public class ConnectionDescriptor { private String address; private String host; public String getAddress() { return address; } public ConnectionDescriptor setAddress(String address) { this.address = address; return this; } public String getHost() { return host; } public ConnectionDescriptor setHost(String host) { this.host = host; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConnectionDescriptor that = (ConnectionDescriptor)o; return Objects.equals(address, that.address); } @Override public int hashCode() { return address != null ? address.hashCode() : 0; } @Override public String toString() { return "ConnectionDescriptor{" + "address='" + address + '\'' + ", host='" + host + '\'' + '}'; } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.util.AssertUtil; /** * The connection group stores connection set for a specific namespace. * * @author Eric Zhao * @since 1.4.0 */ public class ConnectionGroup { private final String namespace; private final Set connectionSet = new CopyOnWriteArraySet<>(); private final AtomicInteger connectedCount = new AtomicInteger(); public ConnectionGroup(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); this.namespace = namespace; } public ConnectionGroup() { this(ServerConstants.DEFAULT_NAMESPACE); } public ConnectionGroup addConnection(String address) { AssertUtil.notEmpty(address, "address cannot be empty"); String[] ip = address.split(":"); String host; if (ip != null && ip.length >= 1) { host = ip[0]; } else { host = address; } boolean newAdded = connectionSet.add(new ConnectionDescriptor().setAddress(address).setHost(host)); if (newAdded) { connectedCount.incrementAndGet(); } return this; } public ConnectionGroup removeConnection(String address) { AssertUtil.notEmpty(address, "address cannot be empty"); if (connectionSet.remove(new ConnectionDescriptor().setAddress(address))) { connectedCount.decrementAndGet(); } return this; } public String getNamespace() { return namespace; } public Set getConnectionSet() { return connectionSet; } public int getConnectedCount() { return connectedCount.get(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Manager for namespace-scope {@link ConnectionGroup}. * * @author Eric Zhao * @since 1.4.0 */ public final class ConnectionManager { /** * Connection map (namespace, connection). */ private static final Map CONN_MAP = new ConcurrentHashMap<>(); /** * namespace map (address, namespace). */ private static final Map NAMESPACE_MAP = new ConcurrentHashMap<>(); /** * Get connected count for specific namespace. * * @param namespace namespace to check * @return connected count for specific namespace */ public static int getConnectedCount(String namespace) { AssertUtil.notEmpty(namespace, "namespace should not be empty"); ConnectionGroup group = CONN_MAP.get(namespace); return group == null ? 0 : group.getConnectedCount(); } public static ConnectionGroup getOrCreateGroup(String namespace) { AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); ConnectionGroup group = CONN_MAP.get(namespace); if (group == null) { synchronized (CREATE_LOCK) { if ((group = CONN_MAP.get(namespace)) == null) { group = new ConnectionGroup(namespace); CONN_MAP.put(namespace, group); } } } return group; } public static void removeConnection(String address) { AssertUtil.assertNotBlank(address, "address should not be empty"); String namespace = NAMESPACE_MAP.get(address); if (namespace != null) { ConnectionGroup group = CONN_MAP.get(namespace); if (group == null) { return; } group.removeConnection(address); RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace); } NAMESPACE_MAP.remove(address); } public static void removeConnection(String namespace, String address) { AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); AssertUtil.assertNotBlank(address, "address should not be empty"); ConnectionGroup group = CONN_MAP.get(namespace); if (group == null) { return; } group.removeConnection(address); NAMESPACE_MAP.remove(address); RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace); } public static ConnectionGroup addConnection(String namespace, String address) { AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); AssertUtil.assertNotBlank(address, "address should not be empty"); ConnectionGroup group = getOrCreateGroup(namespace); group.addConnection(address); NAMESPACE_MAP.put(address, namespace); RecordLog.info("[ConnectionManager] Client <{}> registered with namespace <{}>", address, namespace); return group; } public static ConnectionGroup getOrCreateConnectionGroup(String namespace) { AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); ConnectionGroup group = getOrCreateGroup(namespace); return group; } public static ConnectionGroup getConnectionGroup(String namespace) { AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); ConnectionGroup group = CONN_MAP.get(namespace); return group; } public static boolean isClientOnline(String address){ return NAMESPACE_MAP.containsKey(address); } static void clear() { CONN_MAP.clear(); NAMESPACE_MAP.clear(); } private static final Object CREATE_LOCK = new Object(); private ConnectionManager() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.channel.Channel; /** * Universal connection pool for connection management. * * @author xuyue * @author Eric Zhao * @since 1.4.0 */ public class ConnectionPool { @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService TIMER = Executors.newScheduledThreadPool(2); /** * Format: ("ip:port", connection) */ private final Map CONNECTION_MAP = new ConcurrentHashMap(); /** * Periodic scan task. */ private ScheduledFuture scanTaskFuture = null; public void createConnection(Channel channel) { if (channel != null) { Connection connection = new NettyConnection(channel, this); String connKey = getConnectionKey(channel); CONNECTION_MAP.put(connKey, connection); } } /** * Start the scan task for long-idle connections. */ private synchronized void startScan() { if (scanTaskFuture == null || scanTaskFuture.isCancelled() || scanTaskFuture.isDone()) { scanTaskFuture = TIMER.scheduleAtFixedRate( new ScanIdleConnectionTask(this), 10, 30, TimeUnit.SECONDS); } } /** * Format to "ip:port". * * @param channel channel * @return formatted key */ private String getConnectionKey(Channel channel) { InetSocketAddress socketAddress = (InetSocketAddress)channel.remoteAddress(); String remoteIp = socketAddress.getAddress().getHostAddress(); int remotePort = socketAddress.getPort(); return remoteIp + ":" + remotePort; } private String getConnectionKey(String ip, int port) { return ip + ":" + port; } public void refreshLastReadTime(Channel channel) { if (channel != null) { String connKey = getConnectionKey(channel); Connection connection = CONNECTION_MAP.get(connKey); if (connection != null) { connection.refreshLastReadTime(System.currentTimeMillis()); } } } public Connection getConnection(String remoteIp, int remotePort) { String connKey = getConnectionKey(remoteIp, remotePort); return CONNECTION_MAP.get(connKey); } public void remove(Channel channel) { String connKey = getConnectionKey(channel); CONNECTION_MAP.remove(connKey); } public List listAllConnection() { List connections = new ArrayList(CONNECTION_MAP.values()); return connections; } public int count() { return CONNECTION_MAP.size(); } public void clear() { CONNECTION_MAP.clear(); } public void shutdownAll() throws Exception { for (Connection c : CONNECTION_MAP.values()) { c.close(); } } public void refreshIdleTask() { if (scanTaskFuture == null || scanTaskFuture.cancel(false)) { startScan(); } else { RecordLog.info("The result of canceling scanTask is error."); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.connection; import java.net.InetSocketAddress; import java.net.SocketAddress; import io.netty.channel.Channel; /** * @author xuyue * @since 1.4.0 */ public class NettyConnection implements Connection { private String remoteIp; private int remotePort; private Channel channel; private long lastReadTime; private ConnectionPool pool; public NettyConnection(Channel channel, ConnectionPool pool) { this.channel = channel; this.pool = pool; InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); this.remoteIp = socketAddress.getAddress().getHostAddress(); this.remotePort = socketAddress.getPort(); this.lastReadTime = System.currentTimeMillis(); } @Override public SocketAddress getLocalAddress() { return channel.localAddress(); } @Override public int getRemotePort() { return remotePort; } @Override public String getRemoteIP() { return remoteIp; } @Override public void refreshLastReadTime(long lastReadTime) { this.lastReadTime = lastReadTime; } @Override public long getLastReadTime() { return lastReadTime; } @Override public String getConnectionKey() { return remoteIp + ":" + remotePort; } @Override public void close() { // Remove from connection pool. pool.remove(channel); // Close the connection. if (channel != null && channel.isActive()){ channel.close(); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java ================================================ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.List; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author xuyue * @author Eric Zhao * @since 1.4.0 */ public class ScanIdleConnectionTask implements Runnable { private final ConnectionPool connectionPool; public ScanIdleConnectionTask(ConnectionPool connectionPool) { this.connectionPool = connectionPool; } @Override public void run() { try { int idleSeconds = ClusterServerConfigManager.getIdleSeconds(); long idleTimeMillis = idleSeconds * 1000; if (idleTimeMillis < 0) { idleTimeMillis = ServerTransportConfig.DEFAULT_IDLE_SECONDS * 1000; } long now = System.currentTimeMillis(); List connections = connectionPool.listAllConnection(); for (Connection conn : connections) { if ((now - conn.getLastReadTime()) > idleTimeMillis) { RecordLog.info("[ScanIdleConnectionTask] The connection <{}:{}> has been idle for <{}>s. It will be closed now.", conn.getRemoteIP(), conn.getRemotePort(), idleSeconds); conn.close(); } } } catch (Throwable t) { RecordLog.warn("[ScanIdleConnectionTask] Failed to clean-up idle tasks", t); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.handler; import java.net.InetSocketAddress; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool; import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor; import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Netty server handler for Sentinel token server. * * @author Eric Zhao * @since 1.4.0 */ public class TokenServerHandler extends ChannelInboundHandlerAdapter { private final ConnectionPool globalConnectionPool; public TokenServerHandler(ConnectionPool globalConnectionPool) { this.globalConnectionPool = globalConnectionPool; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { globalConnectionPool.createConnection(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String remoteAddress = getRemoteAddress(ctx); globalConnectionPool.remove(ctx.channel()); ConnectionManager.removeConnection(remoteAddress); } @Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { globalConnectionPool.refreshLastReadTime(ctx.channel()); if (msg instanceof ClusterRequest) { ClusterRequest request = (ClusterRequest)msg; // Client ping with its namespace, add to connection manager. if (request.getType() == ClusterConstants.MSG_TYPE_PING) { handlePingRequest(ctx, request); return; } // Pick request processor for request type. RequestProcessor processor = RequestProcessorProvider.getProcessor(request.getType()); if (processor == null) { RecordLog.warn("[TokenServerHandler] No processor for request type: " + request.getType()); writeBadResponse(ctx, request); } else { ClusterResponse response = processor.processRequest(request); writeResponse(ctx, response); } } } private void writeBadResponse(ChannelHandlerContext ctx, ClusterRequest request) { ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), ClusterConstants.RESPONSE_STATUS_BAD, null); writeResponse(ctx, response); } private void writeResponse(ChannelHandlerContext ctx, ClusterResponse response) { ctx.writeAndFlush(response); } private void handlePingRequest(ChannelHandlerContext ctx, ClusterRequest request) { if (request.getData() == null || StringUtil.isBlank((String)request.getData())) { writeBadResponse(ctx, request); return; } String namespace = (String)request.getData(); String clientAddress = getRemoteAddress(ctx); // Add the remote namespace to connection manager. int curCount = ConnectionManager.addConnection(namespace, clientAddress).getConnectedCount(); int status = ClusterConstants.RESPONSE_STATUS_OK; ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); writeResponse(ctx, response); } private String getRemoteAddress(ChannelHandlerContext ctx) { if (ctx.channel().remoteAddress() == null) { return null; } InetSocketAddress inetAddress = (InetSocketAddress) ctx.channel().remoteAddress(); return inetAddress.getAddress().getHostAddress() + ":" + inetAddress.getPort(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.init; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowRequestDataDecoder; import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowResponseDataWriter; import com.alibaba.csp.sentinel.cluster.server.codec.data.ParamFlowRequestDataDecoder; import com.alibaba.csp.sentinel.cluster.server.codec.data.PingRequestDataDecoder; import com.alibaba.csp.sentinel.cluster.server.codec.data.PingResponseDataWriter; import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry; import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry; import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author Eric Zhao * @since 1.4.0 */ public class DefaultClusterServerInitFunc implements InitFunc { @Override public void init() throws Exception { initDefaultEntityDecoders(); initDefaultEntityWriters(); initDefaultProcessors(); // Eagerly-trigger the SPI pre-load of token service. TokenServiceProvider.getService(); RecordLog.info("[DefaultClusterServerInitFunc] Default entity codec and processors registered"); } private void initDefaultEntityWriters() { ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PING, new PingResponseDataWriter()); ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_FLOW, new FlowResponseDataWriter()); ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PARAM_FLOW, new FlowResponseDataWriter()); } private void initDefaultEntityDecoders() { RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PING, new PingRequestDataDecoder()); RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_FLOW, new FlowRequestDataDecoder()); RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PARAM_FLOW, new ParamFlowRequestDataDecoder()); } private void initDefaultProcessors() { // Eagerly-trigger the SPI pre-load. RequestProcessorProvider.getProcessor(0); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.log; import com.alibaba.csp.sentinel.eagleeye.EagleEye; import com.alibaba.csp.sentinel.eagleeye.StatLogger; import com.alibaba.csp.sentinel.log.LogBase; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterServerStatLogUtil { private static final String FILE_NAME = "sentinel-server.log"; private static StatLogger statLogger; static { String path = LogBase.getLogBaseDir() + FILE_NAME; statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-server-record") .intervalSeconds(1) .entryDelimiter('|') .keyDelimiter(',') .valueDelimiter(',') .maxEntryCount(5000) .configLogFilePath(path) .maxFileSizeMB(300) .maxBackupIndex(3) .buildSingleton(); } public static void log(String msg) { statLogger.stat(msg).count(); } public static void log(String msg, int count) { statLogger.stat(msg).count(count); } private ClusterServerStatLogUtil() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.processor; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; /** * @author Eric Zhao * @since 1.4.0 */ @RequestType(ClusterConstants.MSG_TYPE_FLOW) public class FlowRequestProcessor implements RequestProcessor { @Override public ClusterResponse processRequest(ClusterRequest request) { TokenService tokenService = TokenServiceProvider.getService(); long flowId = request.getData().getFlowId(); int count = request.getData().getCount(); boolean prioritized = request.getData().isPriority(); TokenResult result = tokenService.requestToken(flowId, count, prioritized); return toResponse(result, request); } private ClusterResponse toResponse(TokenResult result, ClusterRequest request) { return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), new FlowTokenResponseData() .setRemainingCount(result.getRemaining()) .setWaitInMs(result.getWaitInMs()) ); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.processor; import java.util.Collection; import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; /** * @author Eric Zhao * @since 1.4.0 */ @RequestType(ClusterConstants.MSG_TYPE_PARAM_FLOW) public class ParamFlowRequestProcessor implements RequestProcessor { @Override public ClusterResponse processRequest(ClusterRequest request) { TokenService tokenService = TokenServiceProvider.getService(); long flowId = request.getData().getFlowId(); int count = request.getData().getCount(); Collection args = request.getData().getParams(); TokenResult result = tokenService.requestParamToken(flowId, count, args); return toResponse(result, request); } private ClusterResponse toResponse(TokenResult result, ClusterRequest request) { return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), new FlowTokenResponseData() .setRemainingCount(result.getRemaining()) .setWaitInMs(0) ); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.processor; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; /** * Interface of cluster request processor. * * @param type of request body * @param type of response body * @author Eric Zhao * @since 1.4.0 */ public interface RequestProcessor { /** * Process the cluster request. * * @param request Sentinel cluster request * @return the response after processed */ ClusterResponse processRequest(ClusterRequest request); } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.processor; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.spi.SpiLoader; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ public final class RequestProcessorProvider { private static final Map PROCESSOR_MAP = new ConcurrentHashMap<>(); static { loadAndInit(); } private static void loadAndInit() { List processors = SpiLoader.of(RequestProcessor.class).loadInstanceList(); for (RequestProcessor processor : processors) { Integer type = parseRequestType(processor); if (type != null) { PROCESSOR_MAP.put(type, processor); } } } private static Integer parseRequestType(RequestProcessor processor) { RequestType requestType = processor.getClass().getAnnotation(RequestType.class); if (requestType != null) { return requestType.value(); } else { return null; } } public static RequestProcessor getProcessor(int type) { return PROCESSOR_MAP.get(type); } static void addProcessorIfAbsent(int type, RequestProcessor processor) { PROCESSOR_MAP.putIfAbsent(type, processor); } static void addProcessor(int type, RequestProcessor processor) { AssertUtil.notNull(processor, "processor cannot be null"); PROCESSOR_MAP.put(type, processor); } private RequestProcessorProvider() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.util; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterRuleUtil { public static boolean validId(Long id) { return id != null && id > 0; } private ClusterRuleUtil() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService ================================================ com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder ================================================ com.alibaba.csp.sentinel.cluster.server.codec.DefaultRequestEntityDecoder ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter ================================================ com.alibaba.csp.sentinel.cluster.server.codec.DefaultResponseEntityWriter ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer ================================================ com.alibaba.csp.sentinel.cluster.server.DefaultEmbeddedTokenServer ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor ================================================ com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor com.alibaba.csp.sentinel.cluster.server.processor.ParamFlowRequestProcessor ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerFlowConfigHandler com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterFlowRulesCommandHandler com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterParamFlowRulesCommandHandler com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerConfigHandler com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerTransportConfigHandler com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyServerNamespaceSetHandler com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterFlowRulesCommandHandler com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterParamFlowRulesCommandHandler com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerInfoCommandHandler com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterMetricCommandHandler ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** * Mock support for {@link TimeUtil} * * @author jason */ @RunWith(MockitoJUnitRunner.class) public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long t) { currentMillis += t; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import static org.junit.Assert.*; /** * Useful for testing clustered flow control. * Only used for test. * * @author Eric Zhao * @since 1.4.0 */ public final class ClusterFlowTestUtil { public static void assertResultPass(TokenResult result) { assertNotNull(result); assertEquals(TokenResultStatus.OK, (int) result.getStatus()); } public static void assertResultBlock(TokenResult result) { assertNotNull(result); assertEquals(TokenResultStatus.BLOCKED, (int) result.getStatus()); } public static void assertResultWait(TokenResult result, int waitInMs) { assertNotNull(result); assertEquals(TokenResultStatus.SHOULD_WAIT, (int) result.getStatus()); assertEquals(waitInMs, result.getWaitInMs()); } public static void sleep(int t) { try { Thread.sleep(t); } catch (InterruptedException e) { e.printStackTrace(); } } private ClusterFlowTestUtil() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterFlowCheckerTest { //@Test public void testAcquireClusterTokenOccupyPass() { long flowId = 98765L; final int threshold = 5; FlowRule clusterRule = new FlowRule("abc") .setCount(threshold) .setClusterMode(true) .setClusterConfig(new ClusterFlowConfig() .setFlowId(flowId) .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL)); int sampleCount = 5; int intervalInMs = 1000; int bucketLength = intervalInMs / sampleCount; ClusterMetric metric = new ClusterMetric(sampleCount, intervalInMs); ClusterMetricStatistics.putMetric(flowId, metric); System.out.println(System.currentTimeMillis()); assertResultPass(tryAcquire(clusterRule, false)); assertResultPass(tryAcquire(clusterRule, false)); sleep(bucketLength); assertResultPass(tryAcquire(clusterRule, false)); sleep(bucketLength); assertResultPass(tryAcquire(clusterRule, true)); assertResultPass(tryAcquire(clusterRule, false)); assertResultBlock(tryAcquire(clusterRule, true)); sleep(bucketLength); assertResultBlock(tryAcquire(clusterRule, false)); assertResultBlock(tryAcquire(clusterRule, false)); sleep(bucketLength); assertResultBlock(tryAcquire(clusterRule, false)); assertResultWait(tryAcquire(clusterRule, true), bucketLength); assertResultBlock(tryAcquire(clusterRule, false)); sleep(bucketLength); assertResultPass(tryAcquire(clusterRule, false)); ClusterMetricStatistics.removeMetric(flowId); } private TokenResult tryAcquire(FlowRule clusterRule, boolean occupy) { return ClusterFlowChecker.acquireClusterToken(clusterRule, 1, occupy); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowCheckerTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author yunfeiyanggzq */ public class ConcurrentClusterFlowCheckerTest extends AbstractTimeBasedTest { @Before public void setUp() { FlowRule rule = new FlowRule(); ClusterFlowConfig config = new ClusterFlowConfig(); config.setResourceTimeout(500); config.setClientOfflineTime(1000); config.setFlowId(179L); config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL); rule.setClusterConfig(config); rule.setClusterMode(true); rule.setCount(10); rule.setResource("test"); rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); ArrayList rules = new ArrayList<>(); rules.add(rule); ClusterFlowRuleManager.registerPropertyIfAbsent("1-name"); ClusterFlowRuleManager.loadRules("1-name", rules); } @Test public void testEasyAcquireAndRelease() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(179L); ArrayList list = new ArrayList<>(); for (int i = 0; i < 10; i++) { TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); Assert.assertTrue("fail to acquire token", result.getStatus() == TokenResultStatus.OK && result.getTokenId() != 0); list.add(result.getTokenId()); } for (int i = 0; i < 10; i++) { TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); Assert.assertTrue("fail to acquire block token", result.getStatus() == TokenResultStatus.BLOCKED); } for (int i = 0; i < 10; i++) { TokenResult result = ConcurrentClusterFlowChecker.releaseConcurrentToken(list.get(i)); Assert.assertTrue("fail to release token", result.getStatus() == TokenResultStatus.RELEASE_OK); } Assert.assertTrue("fail to release token", CurrentConcurrencyManager.get(179L).get() == 0 && TokenCacheNodeManager.getSize() == 0); } } @Test public void testConcurrentAcquireAndRelease() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); final FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(179L); final CountDownLatch countDownLatch = new CountDownLatch(1000); ExecutorService pool = Executors.newFixedThreadPool(100, new NamedThreadFactory("ConcurrentClusterFlowCheckerTest", true) ); for (long i = 0; i < 1000; i++) { Runnable task = () -> { Assert.assertNotNull(rule); TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); String msg = String.format("concurrent control fail %s<%s", CurrentConcurrencyManager.get(179L).get(), rule.getCount()); Assert.assertTrue(msg, CurrentConcurrencyManager.get(179L).get() <= rule.getCount()); if (result.getStatus() == TokenResultStatus.OK) { ConcurrentClusterFlowChecker.releaseConcurrentToken(result.getTokenId()); } countDownLatch.countDown(); }; pool.execute(task); } countDownLatch.await(); pool.shutdown(); assert rule != null; Assert.assertTrue("fail to acquire and release token", CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0); } } @Test public void testReleaseExpiredToken() throws InterruptedException { ConnectionManager.addConnection("test", "127.0.0.1"); FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(179L); for (int i = 0; i < 10; i++) { ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); } Thread.sleep(3000); Assert.assertTrue("fail to acquire and release token", CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManagerTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CurrentConcurrencyManagerTest { @Test public void updateTest() throws InterruptedException { CurrentConcurrencyManager.put(113L, 0); CurrentConcurrencyManager.put(223L, 0); final CountDownLatch countDownLatch = new CountDownLatch(1000); ExecutorService pool = Executors.newFixedThreadPool(100, new NamedThreadFactory("CurrentConcurrencyManagerTest", true) ); for (int i = 0; i < 1000; i++) { Runnable task = () -> { CurrentConcurrencyManager.addConcurrency(113L, 1); CurrentConcurrencyManager.addConcurrency(223L, 2); countDownLatch.countDown(); }; pool.execute(task); } countDownLatch.await(); pool.shutdown(); Assert.assertEquals(1000, CurrentConcurrencyManager.get(113L).get()); Assert.assertEquals(2000, CurrentConcurrencyManager.get(223L).get()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.List; public class TokenCacheNodeManagerTest extends AbstractTimeBasedTest { @Before public void setUp() { FlowRule rule = new FlowRule(); ClusterFlowConfig config = new ClusterFlowConfig(); config.setResourceTimeout(500); config.setClientOfflineTime(1000); config.setFlowId(111L); config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL); rule.setClusterConfig(config); rule.setClusterMode(true); rule.setCount(10); rule.setResource("test"); rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); ArrayList rules = new ArrayList<>(); rules.add(rule); ClusterFlowRuleManager.registerPropertyIfAbsent("1-name"); ClusterFlowRuleManager.loadRules("1-name", rules); } @Test public void testPutTokenCacheNode() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); for (long i = 0; i < 100; i++) { final TokenCacheNode node = new TokenCacheNode(); node.setTokenId(i); node.setFlowId(111L); node.setResourceTimeout(10000L); node.setClientTimeout(10000L); node.setClientAddress("localhost"); if (TokenCacheNodeManager.validToken(node)) { TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node); } } Assert.assertEquals(100, TokenCacheNodeManager.getSize()); for (int i = 0; i < 100; i++) { TokenCacheNodeManager.getTokenCacheNode((long) (Math.random() * 100)); } List keyList = new ArrayList<>(TokenCacheNodeManager.getCacheKeySet()); for (int i = 0; i < 100; i++) { Assert.assertEquals(i, (long) keyList.get(i)); TokenCacheNodeManager.removeTokenCacheNode(i); } } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/GlobalRequestLimiterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; public class GlobalRequestLimiterTest extends AbstractTimeBasedTest { @Before public void preTest() { ClusterServerConfigManager.setMaxAllowedQps(3); } @Test public void testPass() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); GlobalRequestLimiter.initIfAbsent("user"); Assert.assertNotNull(GlobalRequestLimiter.getRequestLimiter("user")); Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("user"), 0.01); Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); Assert.assertFalse(GlobalRequestLimiter.tryPass("user")); Assert.assertEquals(3, GlobalRequestLimiter.getCurrentQps("user"), 0.01); // wait a second to refresh the window sleep(mocked, 1000); Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); Assert.assertEquals(2, GlobalRequestLimiter.getCurrentQps("user"), 0.01); } } @Test public void testChangeMaxAllowedQps() { GlobalRequestLimiter.initIfAbsent("foo"); Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01); GlobalRequestLimiter.applyMaxQpsChange(10); Assert.assertEquals(10, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/RequestLimiterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import org.mockito.MockedStatic; import static org.junit.Assert.*; public class RequestLimiterTest extends AbstractTimeBasedTest { @Test public void testRequestLimiter() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); RequestLimiter limiter = new RequestLimiter(10); limiter.add(3); limiter.add(3); limiter.add(3); assertTrue(limiter.canPass()); assertEquals(9, limiter.getSum()); limiter.add(3); assertFalse(limiter.canPass()); // wait a second to refresh the window sleep(mocked, 1000); limiter.add(3); assertTrue(limiter.tryPass()); assertTrue(limiter.canPass()); assertEquals(4, limiter.getSum()); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; public class ClusterMetricTest extends AbstractTimeBasedTest { @Test public void testTryOccupyNext() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); ClusterMetric metric = new ClusterMetric(5, 25); metric.add(ClusterFlowEvent.PASS, 1); metric.add(ClusterFlowEvent.PASS, 2); metric.add(ClusterFlowEvent.PASS, 1); metric.add(ClusterFlowEvent.BLOCK, 1); Assert.assertEquals(4, metric.getSum(ClusterFlowEvent.PASS)); Assert.assertEquals(1, metric.getSum(ClusterFlowEvent.BLOCK)); Assert.assertEquals(160, metric.getAvg(ClusterFlowEvent.PASS), 0.01); Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 111, 900)); metric.add(ClusterFlowEvent.PASS, 1); metric.add(ClusterFlowEvent.PASS, 2); metric.add(ClusterFlowEvent.PASS, 1); Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 222, 900)); metric.add(ClusterFlowEvent.PASS, 1); metric.add(ClusterFlowEvent.PASS, 2); metric.add(ClusterFlowEvent.PASS, 1); Assert.assertEquals(0, metric.tryOccupyNext(ClusterFlowEvent.PASS, 333, 900)); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetricTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; import com.alibaba.csp.sentinel.cluster.server.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import java.util.HashMap; import java.util.Map; public class ClusterParamMetricTest extends AbstractTimeBasedTest { @Test public void testClusterParamMetric() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, System.currentTimeMillis()); Map topMap = new HashMap(); ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100); metric.addValue("e1", -1); metric.addValue("e1", -2); metric.addValue("e2", 100); metric.addValue("e2", 23); metric.addValue("e3", 100); metric.addValue("e3", 230); Assert.assertEquals(-3, metric.getSum("e1")); Assert.assertEquals(-120, metric.getAvg("e1"), 0.01); topMap.put("e3", (double) 13200); Assert.assertEquals(topMap, metric.getTopValues(1)); topMap.put("e2", (double) 4920); topMap.put("e1", (double) -120); Assert.assertEquals(topMap, metric.getTopValues(5)); metric.addValue("e2", 100); metric.addValue("e2", 23); Assert.assertEquals(246, metric.getSum("e2")); Assert.assertEquals(9840, metric.getAvg("e2"), 0.01); } } @Test(expected = IllegalArgumentException.class) public void testIllegalArgument() { ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100); metric.getTopValues(-1); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** * Mock support for {@link TimeUtil}. */ @RunWith(MockitoJUnitRunner.class) public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long t) { currentMillis += t; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriterTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.codec.data; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Test cases for {@link PingResponseDataWriter}. * * @author Eric Zhao */ public class PingResponseDataWriterTest { @Test public void testWritePingResponseAndParse() { ByteBuf buf = Unpooled.buffer(); PingResponseDataWriter writer = new PingResponseDataWriter(); int small = 120; writer.writeTo(small, buf); assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4); assertThat(buf.readInt()).isEqualTo(small); int big = Integer.MAX_VALUE; writer.writeTo(big, buf); assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4); assertThat(buf.readInt()).isEqualTo(big); buf.release(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManagerTest.java ================================================ package com.alibaba.csp.sentinel.cluster.server.config; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ClusterServerConfigManagerTest { @Test public void testIsValidTransportConfig() { ServerTransportConfig badConfig1 = new ServerTransportConfig().setPort(-1); ServerTransportConfig badConfig2 = new ServerTransportConfig().setPort(886622); ServerTransportConfig goodConfig1 = new ServerTransportConfig().setPort(23456); assertFalse(ClusterServerConfigManager.isValidTransportConfig(badConfig1)); assertFalse(ClusterServerConfigManager.isValidTransportConfig(badConfig2)); assertTrue(ClusterServerConfigManager.isValidTransportConfig(goodConfig1)); } @Test public void testIsValidFlowConfig() { ServerFlowConfig badConfig1 = new ServerFlowConfig().setMaxAllowedQps(-2); ServerFlowConfig badConfig2 = new ServerFlowConfig().setIntervalMs(1000).setSampleCount(3); ServerFlowConfig badConfig3 = new ServerFlowConfig().setIntervalMs(1000).setSampleCount(0); assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig1)); assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig2)); assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig3)); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroupTest.java ================================================ package com.alibaba.csp.sentinel.cluster.server.connection; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ConnectionGroupTest { @Test public void testAddAndRemoveConnection() { String namespace = "test-conn-group"; ConnectionGroup group = new ConnectionGroup(namespace); assertEquals(0, group.getConnectedCount()); String address1 = "12.23.34.45:5566"; String address2 = "192.168.0.22:32123"; String address3 = "12.23.34.45:5566"; group.addConnection(address1); assertEquals(1, group.getConnectedCount()); group.addConnection(address2); assertEquals(2, group.getConnectedCount()); group.addConnection(address3); assertEquals(2, group.getConnectedCount()); group.removeConnection(address1); assertEquals(1, group.getConnectedCount()); group.removeConnection(address3); assertEquals(1, group.getConnectedCount()); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManagerTest.java ================================================ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ConnectionManagerTest { @Before public void setUp() { ConnectionManager.clear(); } @After public void cleanUp() { ConnectionManager.clear(); } @Test public void testAndConnectionAndGetConnectedCount() { String namespace = "test-namespace"; assertEquals(0, ConnectionManager.getConnectedCount(namespace)); // Put one connection. ConnectionManager.addConnection(namespace, "12.23.34.45:1997"); assertEquals(1, ConnectionManager.getConnectedCount(namespace)); // Put duplicate connection. ConnectionManager.addConnection(namespace, "12.23.34.45:1997"); assertEquals(1, ConnectionManager.getConnectedCount(namespace)); // Put another connection. ConnectionManager.addConnection(namespace, "12.23.34.49:22123"); assertEquals(2, ConnectionManager.getConnectedCount(namespace)); } @Test(expected = IllegalArgumentException.class) public void testGetOrCreateGroupBadNamespace() { ConnectionManager.getOrCreateGroup(""); } @Test public void testGetOrCreateGroupMultipleThread() throws Exception { final String namespace = "test-namespace"; int threadCount = 32; final List groups = new CopyOnWriteArrayList<>(); final CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(new Runnable() { @Override public void run() { groups.add(ConnectionManager.getOrCreateGroup(namespace)); latch.countDown(); } }).start(); } latch.await(); for (int i = 1; i < groups.size(); i++) { assertSame(groups.get(i - 1).getNamespace(), groups.get(i).getNamespace()); } } @Test public void testRemoveConnection() { String namespace = "test-namespace-remove"; String address1 = "12.23.34.45:1997"; String address2 = "12.23.34.46:1998"; String address3 = "12.23.34.47:1999"; ConnectionManager.addConnection(namespace, address1); ConnectionManager.addConnection(namespace, address2); ConnectionManager.addConnection(namespace, address3); assertEquals(3, ConnectionManager.getConnectedCount(namespace)); ConnectionManager.removeConnection(namespace, address3); assertEquals(2, ConnectionManager.getConnectedCount(namespace)); assertFalse(ConnectionManager.getOrCreateConnectionGroup(namespace).getConnectionSet().contains( new ConnectionDescriptor().setAddress(address3) )); } @Test public void testGetOrCreateConnectionGroup() { String namespace = "test-namespace"; assertNull(ConnectionManager.getConnectionGroup(namespace)); ConnectionGroup group1 = ConnectionManager.getOrCreateConnectionGroup(namespace); assertNotNull(group1); // Put one connection. ConnectionManager.addConnection(namespace, "12.23.34.45:1997"); ConnectionGroup group2 = ConnectionManager.getOrCreateConnectionGroup(namespace); assertSame(group1, group2); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/Dockerfile ================================================ FROM java:8-jre ENV SENTINEL_HOME /sentinel COPY target/sentinel-envoy-rls-token-server.jar $SENTINEL_HOME/ WORKDIR $SENTINEL_HOME ENTRYPOINT ["sh", "-c"] CMD ["java -Dcsp.sentinel.log.dir=/sentinel/logs/ ${JAVA_OPTS} -jar sentinel-envoy-rls-token-server.jar"] ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md ================================================ # Sentinel Token Server (Envoy RLS implementation) This module provides the [Envoy rate limiting gRPC service](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting#arch-overview-rate-limit) implementation with Sentinel token server. The token server has supported both Envoy RLS v2 and v3 API. Since Envoy 1.18.0, v2 API support has been removed. > Note: the gRPC stub classes for Envoy RLS service is generated via `protobuf-maven-plugin` during the `compile` goal. > The generated classes is located in the directory: `target/generated-sources/protobuf`. ## Build Build the executable jar: ```bash mvn clean package -P prod ``` ## Rule configuration Sentinel RLS token server supports dynamic rule configuration via the yaml file. The file may provide rules for one *domain* (defined in Envoy's conf file). In Envoy, one rate limit request might carry multiple *rate limit descriptors* (which will be generated from [Envoy rate limit actions](https://www.envoyproxy.io/docs/envoy/v1.20.1/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit)). One rate limit descriptor may have multiple entries (key-value pair). We may set different threshold for each rate limit descriptors. A sample rule configuration file: ```yaml domain: foo descriptors: - resources: - key: "destination_cluster" value: "service_httpbin" count: 1 ``` This rule only takes effect for domain `foo`. It will limit the max QPS to 1 for all requests targeted to the `service_httpbin` cluster. We need to provide the path to yaml file via the `SENTINEL_RLS_RULE_FILE_PATH` env (or `-Dcsp.sentinel.rls.rule.file` opts). Then as soon as the content in the rule file has been changed, Sentinel will reload the new rules from the file to the `EnvoyRlsRuleManager`. We may check the logs in `~/logs/csp/sentinel-record.log.xxx` to see whether the rules has been loaded. We may also retrieve the converted `FlowRule` via the command API `localhost:8719/cluster/server/flowRules`. ## Configuration items The configuration list: | Item (env) | Item (JVM property) | Description | Default Value | Required | | -------- | -------- | -------- | -------- | -------- | | `SENTINEL_RLS_GRPC_PORT` | `csp.sentinel.grpc.server.port` | The RLS gRPC server port | **10240** | false | | `SENTINEL_RLS_RULE_FILE_PATH` | `csp.sentinel.rls.rule.file` | The path of the RLS rule yaml file | - | **true** | | `SENTINEL_RLS_ACCESS_LOG` | - | Whether to enable the access log (`on` for enable) | off | false | ## Samples - [Kubernetes sample](./sample/k8s) ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml ================================================ com.alibaba.csp sentinel-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-cluster-server-envoy-rls 1.8 1.8 3.21.9 1.51.0 3.2.1 com.alibaba.csp sentinel-cluster-server-default ${project.version} com.alibaba.csp sentinel-datasource-extension com.alibaba.csp sentinel-transport-simple-http javax.annotation javax.annotation-api ${javax.annotation-api.version} io.grpc grpc-netty ${grpc.version} io.grpc grpc-protobuf ${grpc.version} io.grpc grpc-stub ${grpc.version} com.google.protobuf protobuf-java ${protobuf.version} org.yaml snakeyaml 1.32 junit junit test org.mockito mockito-core test kr.motd.maven os-maven-plugin 1.6.2 org.xolstice.maven.plugins protobuf-maven-plugin 0.6.1 com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} compile compile-custom org.apache.maven.plugins maven-pmd-plugin ${maven.pmd.version} target/generated-sources prod org.apache.maven.plugins maven-shade-plugin ${maven.shade.version} package shade sentinel-envoy-rls-token-server com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsServer ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/README.md ================================================ # Sentinel Envoy RLS - Kubernetes sample This sample will illustrate how to use Sentinel RLS token server with Envoy in Kubernetes clusters. ## Build the Docker image We could use the pre-built Docker image: `registry.cn-hangzhou.aliyuncs.com/sentinel-docker-repo/sentinel-envoy-rls-server:latest` We can also manually build the Docker image in the `sentinel-cluster-server-envoy-rls` directory: ```bash docker build -t "sentinel/sentinel-envoy-rls-server:latest" -f ./Dockerfile . ``` ## Deploy Sentinel RLS token server Next we could deploy the Sentinel RLS token server in the K8S cluster. We've provided a deployment template for Sentinel RLS token server in `sentinel-rls.yml`. It includes: - A `ConfigMap` that contains the cluster flow control rule for Envoy global rate limiting. This will be mounted as a file in the target `Deployment`, so that the Sentinel RLS token server could load the rules dynamically as soon as the rule in the `ConfigMap` has been updated. - A `Deployment` for Sentinel RLS token server. It will mount the `ConfigMap` as a volume for dynamic rule configuration. - A `Service` that exports the Sentinel command port (8719) and the RLS gRPC port (by default 10245) on a cluster-internal IP so that the Envoy pods could communicate with the RLS server. The sample rate limiting rule in the `sentinel-rule-cm`: ```yaml domain: foo descriptors: # For requests to the "service_httpbin" cluster, limit the max QPS to 1 - resources: - key: "destination_cluster" value: "service_httpbin" count: 1 ``` You may enable the access log in the Sentinel RLS token server (output to console) via the `SENTINEL_RLS_ACCESS_LOG` env: ```yaml env: - name: SENTINEL_RLS_ACCESS_LOG value: "on" ``` You may also append JVM opts via the `JAVA_OPTS` env. After preparing the yaml template, you may deploy the Sentinel RLS token server: ```bash kubectl apply -f sample/k8s/sentinel-rls.yml ``` ## Deploy Envoy Next we could deploy the Envoy instances in the K8S cluster. If you've already had Envoy instances running, you could configure the address (`sentinel-rls-service`) and the port (`10245`) of the rate limit cluster in your Envoy configuration. We've provided a deployment template for Envoy in `envoy.yml`. It includes: - A `ConfigMap` that contains the configuration for Envoy. This will be mounted as a file in the target `Deployment`, which will be loaded as the configuration file by Envoy. - A `Deployment` for Envoy. It will mount the `ConfigMap` as a volume for configuration. - A `Service` that exports the Envoy endpoint port (by default 10000) on a cluster-internal IP so that it could be accessible from other pods. If you need external access, you could choose the `LoadBalancer` type or add a frontend ingress. In the sample, we have two [Envoy clusters](https://www.envoyproxy.io/docs/envoy/latest/api-v2/clusters/clusters): - `service_httpbin`: HTTP proxy to `httpbin.org` - `rate_limit_cluster`: the cluster of the Sentinel RLS token server This route configuration tells Envoy to route incoming requests to `httpbin.org`. In the `http_filters` conf item, we added the `envoy.rate_limit` filter to the filter chain so that the global rate limiting is enabled. We set the rate limit domain as `foo`, which matches the item in the Envoy RLS rule. In the `route_config`, we provide the rate limit action: `{destination_cluster: {}}`, which will generate the rate limit descriptor containing the actual target cluster name (e.g. `service_httpbin`). Then we could set rate limit rules for each target clusters. After preparing the yaml template, you may deploy the Envoy instance: ```bash kubectl apply -f sample/k8s/envoy-v3-api.yml ``` ## Test the rate limiting Now it's show time! We could visit the URL `envoy-service:10000/json` in K8S pods. Since we set the max QPS to 1, we could emit concurrent requests to the URL, and we could see the first request passes, while the latter requests are blocked (status 429): ![image](https://user-images.githubusercontent.com/9434884/68571798-d0a46500-049e-11ea-8488-5e90f56f23a5.png) ## Update the rules dynamically You could update the rules in the `sentinel-rule-cm` ConfigMap. Once the content is updated, Sentinel will perceive the changes and load the new rules to `EnvoyRlsRuleManager`. ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/envoy-legacy-v2-api.yml ================================================ # NOTE: legacy V2 API sample, which has been deprecated apiVersion: v1 kind: ConfigMap metadata: name: envoy-cm data: envoy-yml: |- admin: access_log_path: /tmp/admin_access.log address: socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: host_rewrite: httpbin.org cluster: service_httpbin rate_limits: - stage: 0 actions: - {destination_cluster: {}} http_filters: - name: envoy.rate_limit config: domain: foo stage: 0 rate_limit_service: grpc_service: envoy_grpc: cluster_name: rate_limit_cluster timeout: 0.25s - name: envoy.router clusters: - name: service_httpbin connect_timeout: 0.5s type: LOGICAL_DNS # Comment out the following line to test on v6 networks dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: service_httpbin endpoints: - lb_endpoints: - endpoint: address: socket_address: address: httpbin.org port_value: 80 - name: rate_limit_cluster type: LOGICAL_DNS connect_timeout: 0.25s lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: rate_limit_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: sentinel-rls-service port_value: 10245 --- apiVersion: apps/v1 kind: Deployment metadata: name: envoy-deployment-basic labels: app: envoy spec: replicas: 1 selector: matchLabels: app: envoy template: metadata: labels: app: envoy spec: containers: - name: envoy image: envoyproxy/envoy:v1.12.0 ports: - containerPort: 10000 command: ["envoy"] args: ["-c", "/tmp/envoy/envoy.yaml"] volumeMounts: - name: envoy-config mountPath: /tmp/envoy volumes: - name: envoy-config configMap: name: envoy-cm items: - key: envoy-yml path: envoy.yaml --- apiVersion: v1 kind: Service metadata: name: envoy-service labels: name: envoy-service spec: type: ClusterIP ports: - port: 10000 targetPort: 10000 protocol: TCP selector: app: envoy ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/envoy-v3-api.yml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: envoy-cm data: envoy-yml: |- admin: access_log_path: /tmp/admin_access.log address: socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: service_httpbin typed_per_filter_config: envoy.filters.http.dynamic_forward_proxy: "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig host_rewrite_literal: httpbin.org rate_limits: - stage: 0 actions: - {destination_cluster: {}} http_filters: - name: envoy.filters.http.ratelimit typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit domain: foo request_type: external failure_mode_deny: false stage: 0 rate_limit_service: grpc_service: envoy_grpc: cluster_name: rate_limit_cluster timeout: 2s transport_api_version: V3 - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: service_httpbin connect_timeout: 0.5s type: LOGICAL_DNS # Comment out the following line to test on v6 networks dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: service_httpbin endpoints: - lb_endpoints: - endpoint: address: socket_address: address: httpbin.org port_value: 80 - name: rate_limit_cluster type: STRICT_DNS connect_timeout: 10s lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: rate_limit_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: sentinel-rls-service port_value: 10245 --- apiVersion: apps/v1 kind: Deployment metadata: name: envoy-deployment-basic labels: app: envoy spec: replicas: 1 selector: matchLabels: app: envoy template: metadata: labels: app: envoy spec: containers: - name: envoy image: envoyproxy/envoy:v1.20.1 ports: - containerPort: 10000 command: ["envoy"] args: ["-c", "/tmp/envoy/envoy.yaml"] volumeMounts: - name: envoy-config mountPath: /tmp/envoy volumes: - name: envoy-config configMap: name: envoy-cm items: - key: envoy-yml path: envoy.yaml --- apiVersion: v1 kind: Service metadata: name: envoy-service labels: name: envoy-service spec: type: ClusterIP ports: - port: 10000 targetPort: 10000 protocol: TCP selector: app: envoy ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/sentinel-rls.yml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: sentinel-rule-cm data: rule-yaml: |- domain: foo descriptors: - resources: - key: "destination_cluster" value: "service_httpbin" count: 1 --- apiVersion: apps/v1 kind: Deployment metadata: name: sentinel-rls-server labels: app: sentinel spec: replicas: 1 selector: matchLabels: app: sentinel template: metadata: labels: app: sentinel spec: containers: - name: sentinelserver # You could replace the image with your own image here image: "registry.cn-hangzhou.aliyuncs.com/sentinel-docker-repo/sentinel-envoy-rls-server:latest" imagePullPolicy: Always ports: - containerPort: 10245 - containerPort: 8719 volumeMounts: - name: sentinel-rule-config mountPath: /tmp/sentinel env: - name: SENTINEL_RLS_RULE_FILE_PATH value: "/tmp/sentinel/rule.yaml" volumes: - name: sentinel-rule-config configMap: name: sentinel-rule-cm items: - key: rule-yaml path: rule.yaml --- apiVersion: v1 kind: Service metadata: name: sentinel-rls-service labels: name: sentinel-rls-service spec: type: ClusterIP ports: - port: 8719 targetPort: 8719 name: sentinel-command - port: 10245 targetPort: 10245 name: sentinel-grpc selector: app: sentinel ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsConstants.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls; /** * @author Eric Zhao */ public final class SentinelEnvoyRlsConstants { public static final int DEFAULT_GRPC_PORT = 10245; public static final String SERVER_APP_NAME = "sentinel-rls-token-server"; public static final String GRPC_PORT_ENV_KEY = "SENTINEL_RLS_GRPC_PORT"; public static final String GRPC_PORT_PROPERTY_KEY = "csp.sentinel.grpc.server.port"; public static final String RULE_FILE_PATH_ENV_KEY = "SENTINEL_RLS_RULE_FILE_PATH"; public static final String RULE_FILE_PATH_PROPERTY_KEY = "csp.sentinel.rls.rule.file"; public static final String ENABLE_ACCESS_LOG_ENV_KEY = "SENTINEL_RLS_ACCESS_LOG"; private SentinelEnvoyRlsConstants() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServer.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls; import java.util.Optional; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.datasource.EnvoyRlsRuleDataSourceService; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao */ public class SentinelEnvoyRlsServer { public static void main(String[] args) throws Exception { System.setProperty("project.name", SentinelEnvoyRlsConstants.SERVER_APP_NAME); EnvoyRlsRuleDataSourceService dataSourceService = new EnvoyRlsRuleDataSourceService(); dataSourceService.init(); int port = resolvePort(); SentinelRlsGrpcServer server = new SentinelRlsGrpcServer(port); server.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.err.println("[SentinelEnvoyRlsServer] Shutting down gRPC RLS server since JVM is shutting down"); server.shutdown(); dataSourceService.onShutdown(); System.err.println("[SentinelEnvoyRlsServer] Server has been shut down"); })); InitExecutor.doInit(); server.blockUntilShutdown(); } private static int resolvePort() { final int defaultPort = SentinelEnvoyRlsConstants.DEFAULT_GRPC_PORT; // Order: system env > property String portStr = Optional.ofNullable(System.getenv(SentinelEnvoyRlsConstants.GRPC_PORT_ENV_KEY)) .orElse(SentinelConfig.getConfig(SentinelEnvoyRlsConstants.GRPC_PORT_PROPERTY_KEY)); if (StringUtil.isBlank(portStr)) { return defaultPort; } try { int port = Integer.parseInt(portStr); if (port <= 0 || port > 65535) { RecordLog.warn("[SentinelEnvoyRlsServer] Invalid port <" + portStr + ">, using default" + defaultPort); return defaultPort; } return port; } catch (Exception ex) { RecordLog.warn("[SentinelEnvoyRlsServer] Failed to resolve port, using default " + defaultPort); System.err.println("[SentinelEnvoyRlsServer] Failed to resolve port, using default " + defaultPort); return defaultPort; } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImpl.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow.SimpleClusterFlowChecker; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.log.RlsAccessLogger; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.function.Tuple2; import com.google.protobuf.TextFormat; import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor; import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor.Entry; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitRequest; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.Code; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.DescriptorStatus; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.RateLimit; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.RateLimit.Unit; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitServiceGrpc; import io.grpc.stub.StreamObserver; import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR; /** * @author Eric Zhao * @since 1.7.0 */ public class SentinelEnvoyRlsServiceImpl extends RateLimitServiceGrpc.RateLimitServiceImplBase { @Override public void shouldRateLimit(RateLimitRequest request, StreamObserver responseObserver) { int acquireCount = request.getHitsAddend(); if (acquireCount < 0) { responseObserver.onError(new IllegalArgumentException( "acquireCount should be positive, but actual: " + acquireCount)); return; } if (acquireCount == 0) { // Not present, use the default "1" by default. acquireCount = 1; } String domain = request.getDomain(); boolean blocked = false; List statusList = new ArrayList<>(request.getDescriptorsCount()); for (RateLimitDescriptor descriptor : request.getDescriptorsList()) { Tuple2 t = checkToken(domain, descriptor, acquireCount); TokenResult r = t.r2; printAccessLogIfNecessary(domain, descriptor, r); if (r.getStatus() == TokenResultStatus.NO_RULE_EXISTS) { // If the rule of the descriptor is absent, the request will pass directly. r.setStatus(TokenResultStatus.OK); } if (!blocked && r.getStatus() != TokenResultStatus.OK) { blocked = true; } Code statusCode = r.getStatus() == TokenResultStatus.OK ? Code.OK : Code.OVER_LIMIT; DescriptorStatus.Builder descriptorStatusBuilder = DescriptorStatus.newBuilder() .setCode(statusCode); if (t.r1 != null) { descriptorStatusBuilder .setCurrentLimit(RateLimit.newBuilder().setUnit(Unit.SECOND) .setRequestsPerUnit((int)t.r1.getCount()) .build()) .setLimitRemaining(r.getRemaining()); } statusList.add(descriptorStatusBuilder.build()); } Code overallStatus = blocked ? Code.OVER_LIMIT : Code.OK; RateLimitResponse response = RateLimitResponse.newBuilder() .setOverallCode(overallStatus) .addAllStatuses(statusList) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } private void printAccessLogIfNecessary(String domain, RateLimitDescriptor descriptor, TokenResult result) { if (!RlsAccessLogger.isEnabled()) { return; } String message = new StringBuilder("[RlsAccessLog] domain=").append(domain) .append(", descriptor=").append(TextFormat.shortDebugString(descriptor)) .append(", checkStatus=").append(result.getStatus()) .append(", remaining=").append(result.getRemaining()) .toString(); RlsAccessLogger.log(message); } protected Tuple2 checkToken(String domain, RateLimitDescriptor descriptor, int acquireCount) { long ruleId = EnvoySentinelRuleConverter.generateFlowId(generateKey(domain, descriptor)); FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId); if (rule == null) { // Pass if the target rule is absent. return Tuple2.of(null, new TokenResult(TokenResultStatus.NO_RULE_EXISTS)); } // If the rule is present, it should be valid. return Tuple2.of(rule, SimpleClusterFlowChecker.acquireClusterToken(rule, acquireCount)); } private String generateKey(String domain, RateLimitDescriptor descriptor) { StringBuilder sb = new StringBuilder(domain); for (Entry resource : descriptor.getEntriesList()) { sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue()); } return sb.toString(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelRlsGrpcServer.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls; import java.io.IOException; import com.alibaba.csp.sentinel.log.RecordLog; import io.grpc.Server; import io.grpc.ServerBuilder; /** * @author Eric Zhao */ public class SentinelRlsGrpcServer { private final Server server; public SentinelRlsGrpcServer(int port) { ServerBuilder builder = ServerBuilder.forPort(port) .addService(new com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3.SentinelEnvoyRlsServiceImpl()) .addService(new SentinelEnvoyRlsServiceImpl()); server = builder.build(); } public void start() throws IOException { // The gRPC server has already checked the start status, so we don't check here. server.start(); String message = "[SentinelRlsGrpcServer] RLS server is running at port " + server.getPort(); RecordLog.info(message); System.out.println(message); } public void shutdown() { server.shutdownNow(); } public boolean isShutdown() { return server.isShutdown(); } public void blockUntilShutdown() throws InterruptedException { if (server != null) { server.awaitTermination(); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/datasource/EnvoyRlsRuleDataSourceService.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.datasource; import java.util.Arrays; import java.util.List; import java.util.Optional; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsConstants; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRuleManager; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.util.StringUtil; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.representer.Representer; /** * @author Eric Zhao * @since 1.7.0 */ public class EnvoyRlsRuleDataSourceService { private final Yaml yaml; private ReadableDataSource> ds; public EnvoyRlsRuleDataSourceService() { this.yaml = createYamlParser(); } private Yaml createYamlParser() { Representer representer = new Representer(); representer.getPropertyUtils().setSkipMissingProperties(true); return new Yaml(representer); } public synchronized void init() throws Exception { if (ds != null) { return; } String configPath = getRuleConfigPath(); if (StringUtil.isBlank(configPath)) { throw new IllegalStateException("Empty rule config path, please set the file path in the env: " + SentinelEnvoyRlsConstants.RULE_FILE_PATH_ENV_KEY); } this.ds = new FileRefreshableDataSource<>(configPath, s -> Arrays.asList(yaml.loadAs(s, EnvoyRlsRule.class))); EnvoyRlsRuleManager.register2Property(ds.getProperty()); } public synchronized void onShutdown() { if (ds != null) { try { ds.close(); } catch (Exception e) { e.printStackTrace(); } } } private String getRuleConfigPath() { return Optional.ofNullable(System.getenv(SentinelEnvoyRlsConstants.RULE_FILE_PATH_ENV_KEY)) .orElse(SentinelConfig.getConfig(SentinelEnvoyRlsConstants.RULE_FILE_PATH_PROPERTY_KEY)); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/flow/SimpleClusterFlowChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; /** * @author Eric Zhao * @since 1.7.0 */ public final class SimpleClusterFlowChecker { public static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount) { Long id = rule.getClusterConfig().getFlowId(); ClusterMetric metric = ClusterMetricStatistics.getMetric(id); if (metric == null) { return new TokenResult(TokenResultStatus.FAIL); } double latestQps = metric.getAvg(ClusterFlowEvent.PASS); double globalThreshold = rule.getCount() * ClusterServerConfigManager.getExceedCount(); double nextRemaining = globalThreshold - latestQps - acquireCount; if (nextRemaining >= 0) { metric.add(ClusterFlowEvent.PASS, acquireCount); metric.add(ClusterFlowEvent.PASS_REQUEST, 1); ClusterServerStatLogUtil.log("flow|pass|" + id, acquireCount); ClusterServerStatLogUtil.log("flow|pass_request|" + id, 1); // Remaining count is cut down to a smaller integer. return new TokenResult(TokenResultStatus.OK) .setRemaining((int) nextRemaining) .setWaitInMs(0); } else { // Blocked. metric.add(ClusterFlowEvent.BLOCK, acquireCount); metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1); ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount); ClusterServerStatLogUtil.log("flow|block_request|" + id, 1); return blockedResult(); } } private static TokenResult blockedResult() { return new TokenResult(TokenResultStatus.BLOCKED) .setRemaining(0) .setWaitInMs(0); } private SimpleClusterFlowChecker() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/log/RlsAccessLogger.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.log; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsConstants; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao */ public final class RlsAccessLogger { private static boolean enabled = false; static { try { enabled = "on".equalsIgnoreCase(System.getenv(SentinelEnvoyRlsConstants.ENABLE_ACCESS_LOG_ENV_KEY)); } catch (Exception ex) { ex.printStackTrace(); } } public static boolean isEnabled() { return enabled; } public static void log(String info) { if (enabled && StringUtil.isNotEmpty(info)) { System.out.println(info); } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRule.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule; import java.util.List; import java.util.Objects; import java.util.Set; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.7.0 */ public class EnvoyRlsRule { private String domain; private List descriptors; public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } public List getDescriptors() { return descriptors; } public void setDescriptors(List descriptors) { this.descriptors = descriptors; } @Override public String toString() { return "EnvoyRlsRule{" + "domain='" + domain + '\'' + ", descriptors=" + descriptors + '}'; } public static class ResourceDescriptor { private Set resources; private Double count; public ResourceDescriptor() {} public ResourceDescriptor(Set resources, Double count) { this.resources = resources; this.count = count; } public Set getResources() { return resources; } public void setResources(Set resources) { this.resources = resources; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } @Override public String toString() { return "ResourceDescriptor{" + "resources=" + resources + ", count=" + count + '}'; } } public static class KeyValueResource { private String key; private String value; public KeyValueResource() {} public KeyValueResource(String key, String value) { AssertUtil.assertNotBlank(key, "key cannot be blank"); AssertUtil.assertNotBlank(value, "value cannot be blank"); this.key = key; this.value = value; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } KeyValueResource that = (KeyValueResource)o; return Objects.equals(key, that.key) && Objects.equals(value, that.value); } @Override public int hashCode() { return Objects.hash(key, value); } @Override public String toString() { return "KeyValueResource{" + "key='" + key + '\'' + ", value='" + value + '\'' + '}'; } } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.7.0 */ public final class EnvoyRlsRuleManager { private static final ConcurrentMap RULE_MAP = new ConcurrentHashMap<>(); private static final PropertyListener> PROPERTY_LISTENER = new EnvoyRlsRulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(PROPERTY_LISTENER); } /** * Listen to the {@link SentinelProperty} for Envoy RLS rules. The property is the source of {@link EnvoyRlsRule}. * * @param property the property to listen */ public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (PROPERTY_LISTENER) { RecordLog.info("[EnvoyRlsRuleManager] Registering new property to Envoy rate limit service rule manager"); currentProperty.removeListener(PROPERTY_LISTENER); property.addListener(PROPERTY_LISTENER); currentProperty = property; } } /** * Load Envoy RLS rules, while former rules will be replaced. * * @param rules new rules to load * @return true if there are actual changes, otherwise false */ public static boolean loadRules(List rules) { return currentProperty.updateValue(rules); } public static List getRules() { return new ArrayList<>(RULE_MAP.values()); } static final class EnvoyRlsRulePropertyListener extends SimplePropertyListener> { @Override public synchronized void configUpdate(List conf) { Map ruleMap = generateRuleMap(conf); List flowRules = ruleMap.values().stream() .flatMap(e -> EnvoySentinelRuleConverter.toSentinelFlowRules(e).stream()) .collect(Collectors.toList()); RULE_MAP.clear(); RULE_MAP.putAll(ruleMap); RecordLog.info("[EnvoyRlsRuleManager] Envoy RLS rules loaded: {}", flowRules); // Use the "default" namespace. ClusterFlowRuleManager.loadRules(ServerConstants.DEFAULT_NAMESPACE, flowRules); } Map generateRuleMap(List conf) { if (conf == null || conf.isEmpty()) { return new HashMap<>(2); } Map map = new HashMap<>(conf.size()); for (EnvoyRlsRule rule : conf) { if (!isValidRule(rule)) { RecordLog.warn("[EnvoyRlsRuleManager] Ignoring invalid rule when loading new RLS rules: " + rule); continue; } if (map.containsKey(rule.getDomain())) { RecordLog.warn("[EnvoyRlsRuleManager] Ignoring duplicate RLS rule for specific domain: " + rule); continue; } map.put(rule.getDomain(), rule); } return map; } } /** * Check whether the given Envoy RLS rule is valid. * * @param rule the rule to check * @return true if the rule is valid, otherwise false */ public static boolean isValidRule(EnvoyRlsRule rule) { if (rule == null || StringUtil.isBlank(rule.getDomain())) { return false; } List descriptors = rule.getDescriptors(); if (descriptors == null || descriptors.isEmpty()) { return false; } for (EnvoyRlsRule.ResourceDescriptor descriptor : descriptors) { if (descriptor == null || descriptor.getCount() == null || descriptor.getCount() < 0) { return false; } Set resources = descriptor.getResources(); if (resources == null || resources.isEmpty()) { return false; } for (EnvoyRlsRule.KeyValueResource resource : resources) { if (resource == null || StringUtil.isBlank(resource.getKey()) || StringUtil.isBlank(resource.getValue())) { return false; } } } return true; } private EnvoyRlsRuleManager() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule; import java.util.List; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.7.0 */ public final class EnvoySentinelRuleConverter { /** * Currently we use "|" to separate each key/value entries. */ public static final String SEPARATOR = "|"; /** * Convert the {@link EnvoyRlsRule} to a list of Sentinel flow rules. * * @param rule a valid Envoy RLS rule * @return converted rules */ public static List toSentinelFlowRules(EnvoyRlsRule rule) { if (!EnvoyRlsRuleManager.isValidRule(rule)) { throw new IllegalArgumentException("Not a valid RLS rule"); } return rule.getDescriptors().stream() .map(e -> toSentinelFlowRule(rule.getDomain(), e)) .collect(Collectors.toList()); } public static FlowRule toSentinelFlowRule(String domain, EnvoyRlsRule.ResourceDescriptor descriptor) { // One descriptor could have only one rule. String identifier = generateKey(domain, descriptor); long flowId = generateFlowId(identifier); return new FlowRule(identifier) .setCount(descriptor.getCount()) .setClusterMode(true) .setClusterConfig(new ClusterFlowConfig() .setFlowId(flowId) .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL) .setSampleCount(1) .setFallbackToLocalWhenFail(false)); } public static long generateFlowId(String key) { if (StringUtil.isBlank(key)) { return -1L; } // Add offset to avoid negative ID. return (long) Integer.MAX_VALUE + key.hashCode(); } public static String generateKey(String domain, EnvoyRlsRule.ResourceDescriptor descriptor) { AssertUtil.assertNotBlank(domain, "domain cannot be blank"); AssertUtil.notNull(descriptor, "EnvoyRlsRule.ResourceDescriptor cannot be null"); AssertUtil.assertNotEmpty(descriptor.getResources(), "resources in descriptor cannot be null"); StringBuilder sb = new StringBuilder(domain); for (EnvoyRlsRule.KeyValueResource resource : descriptor.getResources()) { sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue()); } return sb.toString(); } private EnvoySentinelRuleConverter() {} } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/service/v3/SentinelEnvoyRlsServiceImpl.java ================================================ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow.SimpleClusterFlowChecker; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.log.RlsAccessLogger; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.function.Tuple2; import com.google.protobuf.TextFormat; import io.envoyproxy.envoy.extensions.common.ratelimit.v3.RateLimitDescriptor; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitRequest; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.Code; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.RateLimit; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.DescriptorStatus; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitServiceGrpc; import io.grpc.stub.StreamObserver; import java.util.ArrayList; import java.util.List; import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR; /** * gRPC限流入口,实现envoy rls v3 api * * @author Winjay chan * @date 2021/8/4 */ public class SentinelEnvoyRlsServiceImpl extends RateLimitServiceGrpc.RateLimitServiceImplBase { @Override public void shouldRateLimit(RateLimitRequest request, StreamObserver responseObserver) { int acquireCount = request.getHitsAddend(); if (acquireCount < 0) { responseObserver.onError(new IllegalArgumentException( "acquireCount should be positive, but actual: " + acquireCount)); return; } if (acquireCount == 0) { // Not present, use the default "1" by default. acquireCount = 1; } String domain = request.getDomain(); boolean blocked = false; List statusList = new ArrayList<>(request.getDescriptorsCount()); for (RateLimitDescriptor descriptor : request.getDescriptorsList()) { Tuple2 t = checkToken(domain, descriptor, acquireCount); TokenResult r = t.r2; printAccessLogIfNecessary(domain, descriptor, r); if (r.getStatus() == TokenResultStatus.NO_RULE_EXISTS) { // If the rule of the descriptor is absent, the request will pass directly. r.setStatus(TokenResultStatus.OK); } if (!blocked && r.getStatus() != TokenResultStatus.OK) { blocked = true; } Code statusCode = r.getStatus() == TokenResultStatus.OK ? Code.OK : Code.OVER_LIMIT; DescriptorStatus.Builder descriptorStatusBuilder = DescriptorStatus.newBuilder() .setCode(statusCode); if (t.r1 != null) { descriptorStatusBuilder .setCurrentLimit(RateLimit.newBuilder().setUnit(RateLimit.Unit.SECOND) .setRequestsPerUnit((int)t.r1.getCount()) .build()) .setLimitRemaining(r.getRemaining()); } statusList.add(descriptorStatusBuilder.build()); } Code overallStatus = blocked ? Code.OVER_LIMIT :Code.OK; RateLimitResponse response = RateLimitResponse.newBuilder() .setOverallCode(overallStatus) .addAllStatuses(statusList) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } private void printAccessLogIfNecessary(String domain, RateLimitDescriptor descriptor, TokenResult result) { if (!RlsAccessLogger.isEnabled()) { return; } String message = new StringBuilder("[RlsAccessLog] domain=").append(domain) .append(", descriptor=").append(TextFormat.shortDebugString(descriptor)) .append(", checkStatus=").append(result.getStatus()) .append(", remaining=").append(result.getRemaining()) .toString(); RlsAccessLogger.log(message); } protected Tuple2 checkToken(String domain, RateLimitDescriptor descriptor, int acquireCount) { long ruleId = EnvoySentinelRuleConverter.generateFlowId(generateKey(domain, descriptor)); FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId); if (rule == null) { // Pass if the target rule is absent. return Tuple2.of(null, new TokenResult(TokenResultStatus.NO_RULE_EXISTS)); } // If the rule is present, it should be valid. return Tuple2.of(rule, SimpleClusterFlowChecker.acquireClusterToken(rule, acquireCount)); } private String generateKey(String domain, RateLimitDescriptor descriptor) { StringBuilder sb = new StringBuilder(domain); for (RateLimitDescriptor.Entry resource : descriptor.getEntriesList()) { sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue()); } return sb.toString(); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/core/base.proto ================================================ syntax = "proto3"; package envoy.api.v2.core; option java_outer_classname = "BaseProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.core"; import "google/protobuf/any.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; // Header name/value pair. message HeaderValue { // Header name. string key = 1 [(validate.rules).string = {min_bytes: 1 max_bytes: 16384}]; // Header value. // // The same :ref:`format specifier ` as used for // :ref:`HTTP access logging ` applies here, however // unknown header values are replaced with the empty string instead of `-`. string value = 2 [(validate.rules).string = {max_bytes: 16384}]; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/ratelimit/ratelimit.proto ================================================ syntax = "proto3"; package envoy.api.v2.ratelimit; option java_outer_classname = "RatelimitProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.ratelimit"; import "validate/validate.proto"; // [#protodoc-title: Common rate limit components] // A RateLimitDescriptor is a list of hierarchical entries that are used by the service to // determine the final rate limit key and overall allowed limit. Here are some examples of how // they might be used for the domain "envoy". // // .. code-block:: cpp // // ["authenticated": "false"], ["remote_address": "10.0.0.1"] // // What it does: Limits all unauthenticated traffic for the IP address 10.0.0.1. The // configuration supplies a default limit for the *remote_address* key. If there is a desire to // raise the limit for 10.0.0.1 or block it entirely it can be specified directly in the // configuration. // // .. code-block:: cpp // // ["authenticated": "false"], ["path": "/foo/bar"] // // What it does: Limits all unauthenticated traffic globally for a specific path (or prefix if // configured that way in the service). // // .. code-block:: cpp // // ["authenticated": "false"], ["path": "/foo/bar"], ["remote_address": "10.0.0.1"] // // What it does: Limits unauthenticated traffic to a specific path for a specific IP address. // Like (1) we can raise/block specific IP addresses if we want with an override configuration. // // .. code-block:: cpp // // ["authenticated": "true"], ["client_id": "foo"] // // What it does: Limits all traffic for an authenticated client "foo" // // .. code-block:: cpp // // ["authenticated": "true"], ["client_id": "foo"], ["path": "/foo/bar"] // // What it does: Limits traffic to a specific path for an authenticated client "foo" // // The idea behind the API is that (1)/(2)/(3) and (4)/(5) can be sent in 1 request if desired. // This enables building complex application scenarios with a generic backend. message RateLimitDescriptor { message Entry { // Descriptor key. string key = 1 [(validate.rules).string = {min_bytes: 1}]; // Descriptor value. string value = 2 [(validate.rules).string = {min_bytes: 1}]; } // Descriptor entries. repeated Entry entries = 1 [(validate.rules).repeated = {min_items: 1}]; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/config/core/v3/base.proto ================================================ syntax = "proto3"; package envoy.config.core.v3; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.config.core.v3"; option java_outer_classname = "BaseProto"; option java_multiple_files = true; option (udpa.annotations.file_status).package_version_status = ACTIVE; // Header name/value pair. message HeaderValue { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.HeaderValue"; // Header name. string key = 1 [(validate.rules).string = {min_len: 1 max_bytes: 16384 well_known_regex: HTTP_HEADER_NAME strict: false}]; // Header value. // // The same :ref:`format specifier ` as used for // :ref:`HTTP access logging ` applies here, however // unknown header values are replaced with the empty string instead of `-`. string value = 2 [ (validate.rules).string = {max_bytes: 16384 well_known_regex: HTTP_HEADER_VALUE strict: false} ]; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/extensions/common/ratelimit/v3/ratelimit.proto ================================================ syntax = "proto3"; package envoy.extensions.common.ratelimit.v3; import "envoy/type/v3/ratelimit_unit.proto"; // import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.common.ratelimit.v3"; option java_outer_classname = "RatelimitProto"; option java_multiple_files = true; option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Common rate limit components] // A RateLimitDescriptor is a list of hierarchical entries that are used by the service to // determine the final rate limit key and overall allowed limit. Here are some examples of how // they might be used for the domain "envoy". // // .. code-block:: cpp // // ["authenticated": "false"], ["remote_address": "10.0.0.1"] // // What it does: Limits all unauthenticated traffic for the IP address 10.0.0.1. The // configuration supplies a default limit for the *remote_address* key. If there is a desire to // raise the limit for 10.0.0.1 or block it entirely it can be specified directly in the // configuration. // // .. code-block:: cpp // // ["authenticated": "false"], ["path": "/foo/bar"] // // What it does: Limits all unauthenticated traffic globally for a specific path (or prefix if // configured that way in the service). // // .. code-block:: cpp // // ["authenticated": "false"], ["path": "/foo/bar"], ["remote_address": "10.0.0.1"] // // What it does: Limits unauthenticated traffic to a specific path for a specific IP address. // Like (1) we can raise/block specific IP addresses if we want with an override configuration. // // .. code-block:: cpp // // ["authenticated": "true"], ["client_id": "foo"] // // What it does: Limits all traffic for an authenticated client "foo" // // .. code-block:: cpp // // ["authenticated": "true"], ["client_id": "foo"], ["path": "/foo/bar"] // // What it does: Limits traffic to a specific path for an authenticated client "foo" // // The idea behind the API is that (1)/(2)/(3) and (4)/(5) can be sent in 1 request if desired. // This enables building complex application scenarios with a generic backend. // // Optionally the descriptor can contain a limit override under a "limit" key, that specifies // the number of requests per unit to use instead of the number configured in the // rate limiting service. message RateLimitDescriptor { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.ratelimit.RateLimitDescriptor"; message Entry { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.ratelimit.RateLimitDescriptor.Entry"; // Descriptor key. string key = 1 [(validate.rules).string = {min_len: 1}]; // Descriptor value. string value = 2 [(validate.rules).string = {min_len: 1}]; } // Override rate limit to apply to this descriptor instead of the limit // configured in the rate limit service. See :ref:`rate limit override // ` for more information. message RateLimitOverride { // The number of requests per unit of time. uint32 requests_per_unit = 1; // The unit of time. type.v3.RateLimitUnit unit = 2 [(validate.rules).enum = {defined_only: true}]; } // Descriptor entries. repeated Entry entries = 1 [(validate.rules).repeated = {min_items: 1}]; // Optional rate limit override to supply to the ratelimit service. RateLimitOverride limit = 2; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/service/ratelimit/v2/rls.proto ================================================ syntax = "proto3"; package envoy.service.ratelimit.v2; option java_outer_classname = "RlsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.ratelimit.v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/ratelimit/ratelimit.proto"; import "validate/validate.proto"; // [#protodoc-title: Rate Limit Service (RLS)] service RateLimitService { // Determine whether rate limiting should take place. rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) { } } // Main message for a rate limit request. The rate limit service is designed to be fully generic // in the sense that it can operate on arbitrary hierarchical key/value pairs. The loaded // configuration will parse the request and find the most specific limit to apply. In addition, // a RateLimitRequest can contain multiple "descriptors" to limit on. When multiple descriptors // are provided, the server will limit on *ALL* of them and return an OVER_LIMIT response if any // of them are over limit. This enables more complex application level rate limiting scenarios // if desired. message RateLimitRequest { // All rate limit requests must specify a domain. This enables the configuration to be per // application without fear of overlap. E.g., "envoy". string domain = 1; // All rate limit requests must specify at least one RateLimitDescriptor. Each descriptor is // processed by the service (see below). If any of the descriptors are over limit, the entire // request is considered to be over limit. repeated api.v2.ratelimit.RateLimitDescriptor descriptors = 2; // Rate limit requests can optionally specify the number of hits a request adds to the matched // limit. If the value is not set in the message, a request increases the matched limit by 1. uint32 hits_addend = 3; } // A response from a ShouldRateLimit call. message RateLimitResponse { enum Code { // The response code is not known. UNKNOWN = 0; // The response code to notify that the number of requests are under limit. OK = 1; // The response code to notify that the number of requests are over limit. OVER_LIMIT = 2; } // Defines an actual rate limit in terms of requests per unit of time and the unit itself. message RateLimit { enum Unit { // The time unit is not known. UNKNOWN = 0; // The time unit representing a second. SECOND = 1; // The time unit representing a minute. MINUTE = 2; // The time unit representing an hour. HOUR = 3; // The time unit representing a day. DAY = 4; } // The number of requests per unit of time. uint32 requests_per_unit = 1; // The unit of time. Unit unit = 2; } message DescriptorStatus { // The response code for an individual descriptor. Code code = 1; // The current limit as configured by the server. Useful for debugging, etc. RateLimit current_limit = 2; // The limit remaining in the current time unit. uint32 limit_remaining = 3; } // The overall response code which takes into account all of the descriptors that were passed // in the RateLimitRequest message. Code overall_code = 1; // A list of DescriptorStatus messages which matches the length of the descriptor list passed // in the RateLimitRequest. This can be used by the caller to determine which individual // descriptors failed and/or what the currently configured limits are for all of them. repeated DescriptorStatus statuses = 2; // [#next-major-version: rename to response_headers_to_add] repeated api.v2.core.HeaderValue headers = 3; // A list of headers to add to the request when forwarded repeated api.v2.core.HeaderValue request_headers_to_add = 4; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/service/ratelimit/v3/rls.proto ================================================ syntax = "proto3"; package envoy.service.ratelimit.v3; import "envoy/config/core/v3/base.proto"; import "envoy/extensions/common/ratelimit/v3/ratelimit.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; // import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.service.ratelimit.v3"; option java_outer_classname = "RlsProto"; option java_multiple_files = true; option java_generic_services = true; option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Rate Limit Service (RLS)] service RateLimitService { // Determine whether rate limiting should take place. rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) { } } // Main message for a rate limit request. The rate limit service is designed to be fully generic // in the sense that it can operate on arbitrary hierarchical key/value pairs. The loaded // configuration will parse the request and find the most specific limit to apply. In addition, // a RateLimitRequest can contain multiple "descriptors" to limit on. When multiple descriptors // are provided, the server will limit on *ALL* of them and return an OVER_LIMIT response if any // of them are over limit. This enables more complex application level rate limiting scenarios // if desired. message RateLimitRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.service.ratelimit.v2.RateLimitRequest"; // All rate limit requests must specify a domain. This enables the configuration to be per // application without fear of overlap. E.g., "envoy". string domain = 1; // All rate limit requests must specify at least one RateLimitDescriptor. Each descriptor is // processed by the service (see below). If any of the descriptors are over limit, the entire // request is considered to be over limit. repeated envoy.extensions.common.ratelimit.v3.RateLimitDescriptor descriptors = 2; // Rate limit requests can optionally specify the number of hits a request adds to the matched // limit. If the value is not set in the message, a request increases the matched limit by 1. uint32 hits_addend = 3; } // A response from a ShouldRateLimit call. // [#next-free-field: 7] message RateLimitResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.service.ratelimit.v2.RateLimitResponse"; enum Code { // The response code is not known. UNKNOWN = 0; // The response code to notify that the number of requests are under limit. OK = 1; // The response code to notify that the number of requests are over limit. OVER_LIMIT = 2; } // Defines an actual rate limit in terms of requests per unit of time and the unit itself. message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.service.ratelimit.v2.RateLimitResponse.RateLimit"; // Identifies the unit of of time for rate limit. // [#comment: replace by envoy/type/v3/ratelimit_unit.proto in v4] enum Unit { // The time unit is not known. UNKNOWN = 0; // The time unit representing a second. SECOND = 1; // The time unit representing a minute. MINUTE = 2; // The time unit representing an hour. HOUR = 3; // The time unit representing a day. DAY = 4; } // A name or description of this limit. string name = 3; // The number of requests per unit of time. uint32 requests_per_unit = 1; // The unit of time. Unit unit = 2; } // Cacheable quota for responses, see documentation for the :ref:`quota // ` field. // [#not-implemented-hide:] message Quota { // Number of matching requests granted in quota. Must be 1 or more. uint32 requests = 1 [(validate.rules).uint32 = {gt: 0}]; oneof expiration_specifier { // Point in time at which the quota expires. google.protobuf.Timestamp valid_until = 2; } } // [#next-free-field: 6] message DescriptorStatus { option (udpa.annotations.versioning).previous_message_type = "envoy.service.ratelimit.v2.RateLimitResponse.DescriptorStatus"; // The response code for an individual descriptor. Code code = 1; // The current limit as configured by the server. Useful for debugging, etc. RateLimit current_limit = 2; // The limit remaining in the current time unit. uint32 limit_remaining = 3; // Duration until reset of the current limit window. google.protobuf.Duration duration_until_reset = 4; // Quota granted for the descriptor. This is a certain number of requests over a period of time. // The client may cache this result and apply the effective RateLimitResponse to future matching // requests containing a matching descriptor without querying rate limit service. // // Quota is available for a request if its descriptor set has cached quota available for all // descriptors. // // If quota is available, a RLS request will not be made and the quota will be reduced by 1 for // all matching descriptors. // // If there is not sufficient quota, there are three cases: // 1. A cached entry exists for a RLS descriptor that is out-of-quota, but not expired. // In this case, the request will be treated as OVER_LIMIT. // 2. Some RLS descriptors have a cached entry that has valid quota but some RLS descriptors // have no cached entry. This will trigger a new RLS request. // When the result is returned, a single unit will be consumed from the quota for all // matching descriptors. // If the server did not provide a quota, such as the quota message is empty for some of // the descriptors, then the request admission is determined by the // :ref:`overall_code `. // 3. All RLS descriptors lack a cached entry, this will trigger a new RLS request, // When the result is returned, a single unit will be consumed from the quota for all // matching descriptors. // If the server did not provide a quota, such as the quota message is empty for some of // the descriptors, then the request admission is determined by the // :ref:`overall_code `. // // When quota expires due to timeout, a new RLS request will also be made. // The implementation may choose to preemptively query the rate limit server for more quota on or // before expiration or before the available quota runs out. // [#not-implemented-hide:] Quota quota = 5; } // The overall response code which takes into account all of the descriptors that were passed // in the RateLimitRequest message. Code overall_code = 1; // A list of DescriptorStatus messages which matches the length of the descriptor list passed // in the RateLimitRequest. This can be used by the caller to determine which individual // descriptors failed and/or what the currently configured limits are for all of them. repeated DescriptorStatus statuses = 2; // A list of headers to add to the response repeated config.core.v3.HeaderValue response_headers_to_add = 3; // A list of headers to add to the request when forwarded repeated config.core.v3.HeaderValue request_headers_to_add = 4; // A response body to send to the downstream client when the response code is not OK. bytes raw_body = 5; // Optional response metadata that will be emitted as dynamic metadata to be consumed by the next // filter. This metadata lives in a namespace specified by the canonical name of extension filter // that requires it: // // - :ref:`envoy.filters.http.ratelimit ` for HTTP filter. // - :ref:`envoy.filters.network.ratelimit ` for network filter. // - :ref:`envoy.filters.thrift.rate_limit ` for Thrift filter. google.protobuf.Struct dynamic_metadata = 6; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/type/v3/ratelimit_unit.proto ================================================ syntax = "proto3"; package envoy.type.v3; import "udpa/annotations/status.proto"; option java_package = "io.envoyproxy.envoy.type.v3"; option java_outer_classname = "RatelimitUnitProto"; option java_multiple_files = true; option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Ratelimit Time Unit] // Identifies the unit of of time for rate limit. enum RateLimitUnit { // The time unit is not known. UNKNOWN = 0; // The time unit representing a second. SECOND = 1; // The time unit representing a minute. MINUTE = 2; // The time unit representing an hour. HOUR = 3; // The time unit representing a day. DAY = 4; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/BUILD ================================================ load("//bazel:api_build_system.bzl", "udpa_proto_package") licenses(["notice"]) # Apache 2 udpa_proto_package() ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/migrate.proto ================================================ syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.migrate". extend google.protobuf.MessageOptions { MigrateAnnotation message_migrate = 171962766; } extend google.protobuf.FieldOptions { FieldMigrateAnnotation field_migrate = 171962766; } extend google.protobuf.EnumOptions { MigrateAnnotation enum_migrate = 171962766; } extend google.protobuf.EnumValueOptions { MigrateAnnotation enum_value_migrate = 171962766; } extend google.protobuf.FileOptions { FileMigrateAnnotation file_migrate = 171962766; } message MigrateAnnotation { // Rename the message/enum/enum value in next version. string rename = 1; } message FieldMigrateAnnotation { // Rename the field in next version. string rename = 1; // Add the field to a named oneof in next version. If this already exists, the // field will join its siblings under the oneof, otherwise a new oneof will be // created with the given name. string oneof_promotion = 2; } message FileMigrateAnnotation { // Move all types in the file to another package, this implies changing proto // file path. string move_to_package = 2; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/security.proto ================================================ syntax = "proto3"; package udpa.annotations; import "udpa/annotations/status.proto"; import "google/protobuf/any.proto"; import "google/protobuf/descriptor.proto"; import "validate/validate.proto"; // All annotations in this file are experimental and subject to change. Their // only consumer today is the Envoy APIs and SecuritAnnotationValidator protoc // plugin in this repository. option (udpa.annotations.file_status).work_in_progress = true; extend google.protobuf.FieldOptions { // Magic number is the 28 most significant bits in the sha256sum of // "udpa.annotations.security". FieldSecurityAnnotation security = 11122993; } // These annotations indicate metadata for the purpose of understanding the // security significance of fields. message FieldSecurityAnnotation { // Field should be set in the presence of untrusted downstreams. bool configure_for_untrusted_downstream = 1; // Field should be set in the presence of untrusted upstreams. bool configure_for_untrusted_upstream = 2; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/sensitive.proto ================================================ syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions { // Magic number is the 28 most significant bits in the sha256sum of "udpa.annotations.sensitive". // When set to true, `sensitive` indicates that this field contains sensitive data, such as // personally identifiable information, passwords, or private keys, and should be redacted for // display by tools aware of this annotation. Note that that this has no effect on standard // Protobuf functions such as `TextFormat::PrintToString`. bool sensitive = 76569463; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/status.proto ================================================ syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.status". extend google.protobuf.FileOptions { StatusAnnotation file_status = 222707719; } enum PackageVersionStatus { // Unknown package version status. UNKNOWN = 0; // This version of the package is frozen. FROZEN = 1; // This version of the package is the active development version. ACTIVE = 2; // This version of the package is the candidate for the next major version. It // is typically machine generated from the active development version. NEXT_MAJOR_VERSION_CANDIDATE = 3; } message StatusAnnotation { // The entity is work-in-progress and subject to breaking changes. bool work_in_progress = 1; // The entity belongs to a package with the given version status. PackageVersionStatus package_version_status = 2; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/udpa/annotations/versioning.proto ================================================ syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; extend google.protobuf.MessageOptions { // Magic number derived from 0x78 ('x') 0x44 ('D') 0x53 ('S') VersioningAnnotation versioning = 7881811; } message VersioningAnnotation { // Track the previous message type. E.g. this message might be // udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This // information is consumed by UDPA via proto descriptors. string previous_message_type = 1; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/validate/validate.proto ================================================ syntax = "proto2"; package validate; option go_package = "github.com/envoyproxy/protoc-gen-validate/validate"; option java_package = "io.envoyproxy.pgv.validate"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; // Validation rules applied at the message level extend google.protobuf.MessageOptions { // Disabled nullifies any validation rules for this message, including any // message fields associated with it that do support validation. optional bool disabled = 1071; // Ignore skips generation of validation methods for this message. optional bool ignored = 1072; } // Validation rules applied at the oneof level extend google.protobuf.OneofOptions { // Required ensures that exactly one the field options in a oneof is set; // validation fails if no fields in the oneof are set. optional bool required = 1071; } // Validation rules applied at the field level extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldRules rules = 1071; } // FieldRules encapsulates the rules for each type of field. Depending on the // field, the correct set should be used to ensure proper validations. message FieldRules { optional MessageRules message = 17; oneof type { // Scalar Field Types FloatRules float = 1; DoubleRules double = 2; Int32Rules int32 = 3; Int64Rules int64 = 4; UInt32Rules uint32 = 5; UInt64Rules uint64 = 6; SInt32Rules sint32 = 7; SInt64Rules sint64 = 8; Fixed32Rules fixed32 = 9; Fixed64Rules fixed64 = 10; SFixed32Rules sfixed32 = 11; SFixed64Rules sfixed64 = 12; BoolRules bool = 13; StringRules string = 14; BytesRules bytes = 15; // Complex Field Types EnumRules enum = 16; RepeatedRules repeated = 18; MapRules map = 19; // Well-Known Field Types AnyRules any = 20; DurationRules duration = 21; TimestampRules timestamp = 22; } } // FloatRules describes the constraints applied to `float` values message FloatRules { // Const specifies that this field must be exactly the specified value optional float const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional float lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional float lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional float gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional float gte = 5; // In specifies that this field must be equal to one of the specified // values repeated float in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated float not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // DoubleRules describes the constraints applied to `double` values message DoubleRules { // Const specifies that this field must be exactly the specified value optional double const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional double lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional double lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional double gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional double gte = 5; // In specifies that this field must be equal to one of the specified // values repeated double in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated double not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Int32Rules describes the constraints applied to `int32` values message Int32Rules { // Const specifies that this field must be exactly the specified value optional int32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional int32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional int32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional int32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional int32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated int32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Int64Rules describes the constraints applied to `int64` values message Int64Rules { // Const specifies that this field must be exactly the specified value optional int64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional int64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional int64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional int64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional int64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated int64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // UInt32Rules describes the constraints applied to `uint32` values message UInt32Rules { // Const specifies that this field must be exactly the specified value optional uint32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional uint32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional uint32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional uint32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional uint32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated uint32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated uint32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // UInt64Rules describes the constraints applied to `uint64` values message UInt64Rules { // Const specifies that this field must be exactly the specified value optional uint64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional uint64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional uint64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional uint64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional uint64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated uint64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated uint64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SInt32Rules describes the constraints applied to `sint32` values message SInt32Rules { // Const specifies that this field must be exactly the specified value optional sint32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sint32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sint32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sint32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sint32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sint32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sint32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SInt64Rules describes the constraints applied to `sint64` values message SInt64Rules { // Const specifies that this field must be exactly the specified value optional sint64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sint64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sint64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sint64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sint64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sint64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sint64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Fixed32Rules describes the constraints applied to `fixed32` values message Fixed32Rules { // Const specifies that this field must be exactly the specified value optional fixed32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional fixed32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional fixed32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional fixed32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional fixed32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated fixed32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated fixed32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Fixed64Rules describes the constraints applied to `fixed64` values message Fixed64Rules { // Const specifies that this field must be exactly the specified value optional fixed64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional fixed64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional fixed64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional fixed64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional fixed64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated fixed64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated fixed64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SFixed32Rules describes the constraints applied to `sfixed32` values message SFixed32Rules { // Const specifies that this field must be exactly the specified value optional sfixed32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sfixed32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sfixed32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sfixed32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sfixed32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sfixed32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sfixed32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SFixed64Rules describes the constraints applied to `sfixed64` values message SFixed64Rules { // Const specifies that this field must be exactly the specified value optional sfixed64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sfixed64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sfixed64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sfixed64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sfixed64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sfixed64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sfixed64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // BoolRules describes the constraints applied to `bool` values message BoolRules { // Const specifies that this field must be exactly the specified value optional bool const = 1; } // StringRules describe the constraints applied to `string` values message StringRules { // Const specifies that this field must be exactly the specified value optional string const = 1; // Len specifies that this field must be the specified number of // characters (Unicode code points). Note that the number of // characters may differ from the number of bytes in the string. optional uint64 len = 19; // MinLen specifies that this field must be the specified number of // characters (Unicode code points) at a minimum. Note that the number of // characters may differ from the number of bytes in the string. optional uint64 min_len = 2; // MaxLen specifies that this field must be the specified number of // characters (Unicode code points) at a maximum. Note that the number of // characters may differ from the number of bytes in the string. optional uint64 max_len = 3; // LenBytes specifies that this field must be the specified number of bytes // at a minimum optional uint64 len_bytes = 20; // MinBytes specifies that this field must be the specified number of bytes // at a minimum optional uint64 min_bytes = 4; // MaxBytes specifies that this field must be the specified number of bytes // at a maximum optional uint64 max_bytes = 5; // Pattern specifes that this field must match against the specified // regular expression (RE2 syntax). The included expression should elide // any delimiters. optional string pattern = 6; // Prefix specifies that this field must have the specified substring at // the beginning of the string. optional string prefix = 7; // Suffix specifies that this field must have the specified substring at // the end of the string. optional string suffix = 8; // Contains specifies that this field must have the specified substring // anywhere in the string. optional string contains = 9; // NotContains specifies that this field cannot have the specified substring // anywhere in the string. optional string not_contains = 23; // In specifies that this field must be equal to one of the specified // values repeated string in = 10; // NotIn specifies that this field cannot be equal to one of the specified // values repeated string not_in = 11; // WellKnown rules provide advanced constraints against common string // patterns oneof well_known { // Email specifies that the field must be a valid email address as // defined by RFC 5322 bool email = 12; // Hostname specifies that the field must be a valid hostname as // defined by RFC 1034. This constraint does not support // internationalized domain names (IDNs). bool hostname = 13; // Ip specifies that the field must be a valid IP (v4 or v6) address. // Valid IPv6 addresses should not include surrounding square brackets. bool ip = 14; // Ipv4 specifies that the field must be a valid IPv4 address. bool ipv4 = 15; // Ipv6 specifies that the field must be a valid IPv6 address. Valid // IPv6 addresses should not include surrounding square brackets. bool ipv6 = 16; // Uri specifies that the field must be a valid, absolute URI as defined // by RFC 3986 bool uri = 17; // UriRef specifies that the field must be a valid URI as defined by RFC // 3986 and may be relative or absolute. bool uri_ref = 18; // Address specifies that the field must be either a valid hostname as // defined by RFC 1034 (which does not support internationalized domain // names or IDNs), or it can be a valid IP (v4 or v6). bool address = 21; // Uuid specifies that the field must be a valid UUID as defined by // RFC 4122 bool uuid = 22; // WellKnownRegex specifies a common well known pattern defined as a regex. KnownRegex well_known_regex = 24; } // This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable // strict header validation. // By default, this is true, and HTTP header validations are RFC-compliant. // Setting to false will enable a looser validations that only disallows // \r\n\0 characters, which can be used to bypass header matching rules. optional bool strict = 25 [default = true]; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 26; } // WellKnownRegex contain some well-known patterns. enum KnownRegex { UNKNOWN = 0; // HTTP header name as defined by RFC 7230. HTTP_HEADER_NAME = 1; // HTTP header value as defined by RFC 7230. HTTP_HEADER_VALUE = 2; } // BytesRules describe the constraints applied to `bytes` values message BytesRules { // Const specifies that this field must be exactly the specified value optional bytes const = 1; // Len specifies that this field must be the specified number of bytes optional uint64 len = 13; // MinLen specifies that this field must be the specified number of bytes // at a minimum optional uint64 min_len = 2; // MaxLen specifies that this field must be the specified number of bytes // at a maximum optional uint64 max_len = 3; // Pattern specifes that this field must match against the specified // regular expression (RE2 syntax). The included expression should elide // any delimiters. optional string pattern = 4; // Prefix specifies that this field must have the specified bytes at the // beginning of the string. optional bytes prefix = 5; // Suffix specifies that this field must have the specified bytes at the // end of the string. optional bytes suffix = 6; // Contains specifies that this field must have the specified bytes // anywhere in the string. optional bytes contains = 7; // In specifies that this field must be equal to one of the specified // values repeated bytes in = 8; // NotIn specifies that this field cannot be equal to one of the specified // values repeated bytes not_in = 9; // WellKnown rules provide advanced constraints against common byte // patterns oneof well_known { // Ip specifies that the field must be a valid IP (v4 or v6) address in // byte format bool ip = 10; // Ipv4 specifies that the field must be a valid IPv4 address in byte // format bool ipv4 = 11; // Ipv6 specifies that the field must be a valid IPv6 address in byte // format bool ipv6 = 12; } // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 14; } // EnumRules describe the constraints applied to enum values message EnumRules { // Const specifies that this field must be exactly the specified value optional int32 const = 1; // DefinedOnly specifies that this field must be only one of the defined // values for this enum, failing on any undefined value. optional bool defined_only = 2; // In specifies that this field must be equal to one of the specified // values repeated int32 in = 3; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int32 not_in = 4; } // MessageRules describe the constraints applied to embedded message values. // For message-type fields, validation is performed recursively. message MessageRules { // Skip specifies that the validation rules of this field should not be // evaluated optional bool skip = 1; // Required specifies that this field must be set optional bool required = 2; } // RepeatedRules describe the constraints applied to `repeated` values message RepeatedRules { // MinItems specifies that this field must have the specified number of // items at a minimum optional uint64 min_items = 1; // MaxItems specifies that this field must have the specified number of // items at a maximum optional uint64 max_items = 2; // Unique specifies that all elements in this field must be unique. This // contraint is only applicable to scalar and enum types (messages are not // supported). optional bool unique = 3; // Items specifies the contraints to be applied to each item in the field. // Repeated message fields will still execute validation against each item // unless skip is specified here. optional FieldRules items = 4; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 5; } // MapRules describe the constraints applied to `map` values message MapRules { // MinPairs specifies that this field must have the specified number of // KVs at a minimum optional uint64 min_pairs = 1; // MaxPairs specifies that this field must have the specified number of // KVs at a maximum optional uint64 max_pairs = 2; // NoSparse specifies values in this field cannot be unset. This only // applies to map's with message value types. optional bool no_sparse = 3; // Keys specifies the constraints to be applied to each key in the field. optional FieldRules keys = 4; // Values specifies the constraints to be applied to the value of each key // in the field. Message values will still have their validations evaluated // unless skip is specified here. optional FieldRules values = 5; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 6; } // AnyRules describe constraints applied exclusively to the // `google.protobuf.Any` well-known type message AnyRules { // Required specifies that this field must be set optional bool required = 1; // In specifies that this field's `type_url` must be equal to one of the // specified values. repeated string in = 2; // NotIn specifies that this field's `type_url` must not be equal to any of // the specified values. repeated string not_in = 3; } // DurationRules describe the constraints applied exclusively to the // `google.protobuf.Duration` well-known type message DurationRules { // Required specifies that this field must be set optional bool required = 1; // Const specifies that this field must be exactly the specified value optional google.protobuf.Duration const = 2; // Lt specifies that this field must be less than the specified value, // exclusive optional google.protobuf.Duration lt = 3; // Lt specifies that this field must be less than the specified value, // inclusive optional google.protobuf.Duration lte = 4; // Gt specifies that this field must be greater than the specified value, // exclusive optional google.protobuf.Duration gt = 5; // Gte specifies that this field must be greater than the specified value, // inclusive optional google.protobuf.Duration gte = 6; // In specifies that this field must be equal to one of the specified // values repeated google.protobuf.Duration in = 7; // NotIn specifies that this field cannot be equal to one of the specified // values repeated google.protobuf.Duration not_in = 8; } // TimestampRules describe the constraints applied exclusively to the // `google.protobuf.Timestamp` well-known type message TimestampRules { // Required specifies that this field must be set optional bool required = 1; // Const specifies that this field must be exactly the specified value optional google.protobuf.Timestamp const = 2; // Lt specifies that this field must be less than the specified value, // exclusive optional google.protobuf.Timestamp lt = 3; // Lte specifies that this field must be less than the specified value, // inclusive optional google.protobuf.Timestamp lte = 4; // Gt specifies that this field must be greater than the specified value, // exclusive optional google.protobuf.Timestamp gt = 5; // Gte specifies that this field must be greater than the specified value, // inclusive optional google.protobuf.Timestamp gte = 6; // LtNow specifies that this must be less than the current time. LtNow // can only be used with the Within rule. optional bool lt_now = 7; // GtNow specifies that this must be greater than the current time. GtNow // can only be used with the Within rule. optional bool gt_now = 8; // Within specifies that this field must be within this duration of the // current time. This constraint can be used alone or with the LtNow and // GtNow rules. optional google.protobuf.Duration within = 9; } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImplTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.function.Tuple2; import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitRequest; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse; import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.Code; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author Eric Zhao */ public class SentinelEnvoyRlsServiceImplTest { @Test public void testShouldRateLimitPass() { SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class); StreamObserver streamObserver = mock(StreamObserver.class); String domain = "testShouldRateLimitPass"; int acquireCount = 1; RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a1").setValue("b1").build()) .build(); RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a2").setValue("b2").build()) .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a3").setValue("b3").build()) .build(); ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class); doNothing().when(streamObserver) .onNext(responseCapture.capture()); doCallRealMethod().when(rlsService).shouldRateLimit(any(), any()); when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder() .addDescriptors(descriptor1) .addDescriptors(descriptor2) .setDomain(domain) .setHitsAddend(acquireCount) .build(); rlsService.shouldRateLimit(rateLimitRequest, streamObserver); RateLimitResponse response = responseCapture.getValue(); assertEquals(Code.OK, response.getOverallCode()); response.getStatusesList() .forEach(e -> assertEquals(Code.OK, e.getCode())); } @Test public void testShouldRatePartialBlock() { SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class); StreamObserver streamObserver = mock(StreamObserver.class); String domain = "testShouldRatePartialBlock"; int acquireCount = 1; RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a1").setValue("b1").build()) .build(); RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a2").setValue("b2").build()) .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a3").setValue("b3").build()) .build(); ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class); doNothing().when(streamObserver) .onNext(responseCapture.capture()); doCallRealMethod().when(rlsService).shouldRateLimit(any(), any()); when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.BLOCKED))); when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder() .addDescriptors(descriptor1) .addDescriptors(descriptor2) .setDomain(domain) .setHitsAddend(acquireCount) .build(); rlsService.shouldRateLimit(rateLimitRequest, streamObserver); RateLimitResponse response = responseCapture.getValue(); assertEquals(Code.OVER_LIMIT, response.getOverallCode()); assertEquals(2, response.getStatusesCount()); assertTrue(response.getStatusesList().stream() .anyMatch(e -> e.getCode().equals(Code.OVER_LIMIT))); assertFalse(response.getStatusesList().stream() .allMatch(e -> e.getCode().equals(Code.OVER_LIMIT))); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverterTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule.KeyValueResource; import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule.ResourceDescriptor; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.junit.Test; import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class EnvoySentinelRuleConverterTest { @Test public void testConvertToSentinelFlowRules() { String domain = "testConvertToSentinelFlowRules"; EnvoyRlsRule rlsRule = new EnvoyRlsRule(); rlsRule.setDomain(domain); List descriptors = new ArrayList<>(); ResourceDescriptor d1 = new ResourceDescriptor(); d1.setCount(10d); d1.setResources(Collections.singleton(new KeyValueResource("k1", "v1"))); descriptors.add(d1); ResourceDescriptor d2 = new ResourceDescriptor(); d2.setCount(20d); d2.setResources(new HashSet<>(Arrays.asList( new KeyValueResource("k2", "v2"), new KeyValueResource("k3", "v3") ))); descriptors.add(d2); rlsRule.setDescriptors(descriptors); List rules = EnvoySentinelRuleConverter.toSentinelFlowRules(rlsRule); final String expectedK1 = domain + SEPARATOR + "k1" + SEPARATOR + "v1"; FlowRule r1 = rules.stream() .filter(e -> e.getResource().equals(expectedK1)) .findAny() .orElseThrow(() -> new AssertionError("the converted rule does not exist, expected key: " + expectedK1)); assertEquals(10d, r1.getCount(), 0.01); final String expectedK2 = domain + SEPARATOR + "k2" + SEPARATOR + "v2" + SEPARATOR + "k3" + SEPARATOR + "v3"; FlowRule r2 = rules.stream() .filter(e -> e.getResource().equals(expectedK2)) .findAny() .orElseThrow(() -> new AssertionError("the converted rule does not exist, expected key: " + expectedK2)); assertEquals(20d, r2.getCount(), 0.01); } } ================================================ FILE: sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/service/v3/SentinelEnvoyRlsServiceImplTest.java ================================================ package com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.function.Tuple2; import io.envoyproxy.envoy.extensions.common.ratelimit.v3.RateLimitDescriptor; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitRequest; import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.*; import static org.mockito.Mockito.when; /** * Created by Winjay * * @author Winjay chan * @date 2021/8/13 16:31 */ public class SentinelEnvoyRlsServiceImplTest { @Test public void testShouldRateLimitPass() { SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class); StreamObserver streamObserver = mock(StreamObserver.class); String domain = "testShouldRateLimitPass"; int acquireCount = 1; RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk1").setValue("rv1").build()) .build(); RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk2").setValue("rv2").build()) .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk3").setValue("rv3").build()) .build(); ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class); doNothing().when(streamObserver) .onNext(responseCapture.capture()); doCallRealMethod().when(rlsService).shouldRateLimit(any(), any()); when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder() .addDescriptors(descriptor1) .addDescriptors(descriptor2) .setDomain(domain) .setHitsAddend(acquireCount) .build(); rlsService.shouldRateLimit(rateLimitRequest, streamObserver); RateLimitResponse response = responseCapture.getValue(); assertEquals(RateLimitResponse.Code.OK, response.getOverallCode()); response.getStatusesList() .forEach(e -> assertEquals(RateLimitResponse.Code.OK, e.getCode())); } @Test public void testShouldRatePartialBlock() { SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class); StreamObserver streamObserver = mock(StreamObserver.class); String domain = "testShouldRatePartialBlock"; int acquireCount = 1; RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk1").setValue("rv1").build()) .build(); RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder() .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk2").setValue("rv2").build()) .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk3").setValue("rv3").build()) .build(); ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class); doNothing().when(streamObserver) .onNext(responseCapture.capture()); doCallRealMethod().when(rlsService).shouldRateLimit(any(), any()); when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.BLOCKED))); when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount))) .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK))); RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder() .addDescriptors(descriptor1) .addDescriptors(descriptor2) .setDomain(domain) .setHitsAddend(acquireCount) .build(); rlsService.shouldRateLimit(rateLimitRequest, streamObserver); RateLimitResponse response = responseCapture.getValue(); assertEquals(RateLimitResponse.Code.OVER_LIMIT, response.getOverallCode()); assertEquals(2, response.getStatusesCount()); assertTrue(response.getStatusesList().stream() .anyMatch(e -> e.getCode().equals(RateLimitResponse.Code.OVER_LIMIT))); assertFalse(response.getStatusesList().stream() .allMatch(e -> e.getCode().equals(RateLimitResponse.Code.OVER_LIMIT))); } } ================================================ FILE: sentinel-core/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-core jar The core of Sentinel junit junit test org.hamcrest hamcrest-core org.hamcrest hamcrest-libray org.mockito mockito-inline test org.awaitility awaitility test org.hamcrest java-hamcrest test org.apache.maven.plugins maven-jar-plugin ${project.version} ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.NullContext; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; /** * The entry for asynchronous resources. * * @author Eric Zhao * @since 0.2.0 */ public class AsyncEntry extends CtEntry { private Context asyncContext; AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context) { super(resourceWrapper, chain, context); } AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context, int count, Object[] args) { super(resourceWrapper, chain, context, count, args); } /** * Remove current entry from local context, but does not exit. */ void cleanCurrentEntryInLocal() { if (context instanceof NullContext) { return; } Context originalContext = context; if (originalContext != null) { Entry curEntry = originalContext.getCurEntry(); if (curEntry == this) { Entry parent = this.parent; originalContext.setCurEntry(parent); if (parent != null) { ((CtEntry)parent).child = null; } } else { String curEntryName = curEntry == null ? "none" : curEntry.resourceWrapper.getName() + "@" + curEntry.hashCode(); String msg = String.format("Bad async context state, expected entry: %s, but actual: %s", getResourceWrapper().getName() + "@" + hashCode(), curEntryName); throw new IllegalStateException(msg); } } } public Context getAsyncContext() { return asyncContext; } /** * The async context should not be initialized until the node for current resource has been set to current entry. */ void initAsyncContext() { if (asyncContext == null) { if (context instanceof NullContext) { asyncContext = context; return; } this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName()) .setOrigin(context.getOrigin()) .setCurEntry(this); } else { RecordLog.warn( "[AsyncEntry] Duplicate initialize of async context for entry: " + resourceWrapper.getName()); } } @Override protected void clearEntryContext() { super.clearEntryContext(); this.asyncContext = null; } @Override protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { exitForContext(asyncContext, count, args); return parent; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.util.VersionUtil; /** * Universal constants of Sentinel. * * @author qinan.qn * @author youji.zj * @author jialiang.linjl * @author Eric Zhao */ public final class Constants { public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.8.9"); public final static int MAX_CONTEXT_NAME_SIZE = 2000; public final static int MAX_SLOT_CHAIN_SIZE = 6000; public final static String ROOT_ID = "machine-root"; public final static String CONTEXT_DEFAULT_NAME = "sentinel_default_context"; /** * A virtual resource identifier for total inbound statistics (since 1.5.0). */ public final static String TOTAL_IN_RESOURCE_NAME = "__total_inbound_traffic__"; /** * A virtual resource identifier for cpu usage statistics (since 1.6.1). */ public final static String CPU_USAGE_RESOURCE_NAME = "__cpu_usage__"; /** * A virtual resource identifier for system load statistics (since 1.6.1). */ public final static String SYSTEM_LOAD_RESOURCE_NAME = "__system_load__"; /** * Global ROOT statistic node that represents the universal parent node. */ public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN), new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON)); /** * Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking. */ public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON); /** * The global switch for Sentinel. */ public static volatile boolean ON = true; /** * Order of default processor slots */ public static final int ORDER_NODE_SELECTOR_SLOT = -10000; public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000; public static final int ORDER_LOG_SLOT = -8000; public static final int ORDER_STATISTIC_SLOT = -7000; public static final int ORDER_AUTHORITY_SLOT = -6000; public static final int ORDER_SYSTEM_SLOT = -5000; public static final int ORDER_FLOW_SLOT = -2000; public static final int ORDER_DEFAULT_CIRCUIT_BREAKER_SLOT = -1500; public static final int ORDER_DEGRADE_SLOT = -1000; private Constants() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.util.LinkedList; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.util.function.BiConsumer; /** * Linked entry within current context. * * @author jialiang.linjl * @author Eric Zhao */ class CtEntry extends Entry { protected Entry parent = null; protected Entry child = null; protected ProcessorSlot chain; protected Context context; protected LinkedList> exitHandlers; CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context) { this(resourceWrapper, chain, context, 1, OBJECTS0); } CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context, int count, Object[] args) { super(resourceWrapper, count, args); this.chain = chain; this.context = context; setUpEntryFor(context); } private void setUpEntryFor(Context context) { // The entry should not be associated to NullContext. if (context instanceof NullContext) { return; } this.parent = context.getCurEntry(); if (parent != null) { ((CtEntry) parent).child = this; } context.setCurEntry(this); } @Override public void exit(int count, Object... args) throws ErrorEntryFreeException { trueExit(count, args); } /** * Note: the exit handlers will be called AFTER onExit of slot chain. */ private void callExitHandlersAndCleanUp(Context ctx) { if (exitHandlers != null && !exitHandlers.isEmpty()) { for (BiConsumer handler : this.exitHandlers) { try { handler.accept(ctx, this); } catch (Exception e) { RecordLog.warn("Error occurred when invoking entry exit handler, current entry: " + resourceWrapper.getName(), e); } } exitHandlers = null; } } protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException { if (context != null) { // Null context should exit without clean-up. if (context instanceof NullContext) { return; } if (context.getCurEntry() != this) { String curEntryNameInContext = context.getCurEntry() == null ? null : context.getCurEntry().getResourceWrapper().getName(); // Clean previous call stack. CtEntry e = (CtEntry) context.getCurEntry(); while (e != null) { e.exit(count, args); e = (CtEntry) e.parent; } String errorMessage = String.format("The order of entry exit can't be paired with the order of entry" + ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext, resourceWrapper.getName()); throw new ErrorEntryFreeException(errorMessage); } else { // Go through the onExit hook of all slots. if (chain != null) { chain.exit(context, resourceWrapper, count, args); } // Go through the existing terminate handlers (associated to this invocation). callExitHandlersAndCleanUp(context); // Restore the call stack. context.setCurEntry(parent); if (parent != null) { ((CtEntry) parent).child = null; } if (parent == null) { // Default context (auto entered) will be exited automatically. if (ContextUtil.isDefaultContext(context)) { ContextUtil.exit(); } } // Clean the reference of context in current entry to avoid duplicate exit. clearEntryContext(); } } } protected void clearEntryContext() { this.context = null; } @Override public void whenTerminate(BiConsumer handler) { if (this.exitHandlers == null) { this.exitHandlers = new LinkedList<>(); } this.exitHandlers.add(handler); } @Override protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { exitForContext(context, count, args); return parent; } @Override public Node getLastNode() { return parent == null ? null : parent.getCurNode(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.SlotChainProvider; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.Rule; /** * {@inheritDoc} * * @author jialiang.linjl * @author leyou(lihao) * @author Eric Zhao * @see Sph */ public class CtSph implements Sph { private static final Object[] OBJECTS0 = new Object[0]; /** * Same resource({@link ResourceWrapper#equals(Object)}) will share the same * {@link ProcessorSlotChain}, no matter in which {@link Context}. */ private static volatile Map chainMap = new HashMap(); private static final Object LOCK = new Object(); private AsyncEntry asyncEntryWithNoChain(ResourceWrapper resourceWrapper, Context context) { AsyncEntry entry = new AsyncEntry(resourceWrapper, null, context); entry.initAsyncContext(); // The async entry will be removed from current context as soon as it has been created. entry.cleanCurrentEntryInLocal(); return entry; } private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException { Context context = ContextUtil.getContext(); if (context instanceof NullContext) { // The {@link NullContext} indicates that the amount of context has exceeded the threshold, // so here init the entry only. No rule checking will be done. return asyncEntryWithNoChain(resourceWrapper, context); } if (context == null) { // Using default context. context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); } // Global switch is turned off, so no rule checking will be done. if (!Constants.ON) { return asyncEntryWithNoChain(resourceWrapper, context); } ProcessorSlot chain = lookProcessChain(resourceWrapper); // Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done. if (chain == null) { return asyncEntryWithNoChain(resourceWrapper, context); } AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context, count, args); try { chain.entry(context, resourceWrapper, null, count, prioritized, args); // Initiate the async context only when the entry successfully passed the slot chain. asyncEntry.initAsyncContext(); // The asynchronous call may take time in background, and current context should not be hanged on it. // So we need to remove current async entry from current context. asyncEntry.cleanCurrentEntryInLocal(); } catch (BlockException e1) { // When blocked, the async entry will be exited on current context. // The async context will not be initialized. asyncEntry.exitForContext(context, count, args); throw e1; } catch (Throwable e1) { // This should not happen, unless there are errors existing in Sentinel internal. // When this happens, async context is not initialized. RecordLog.warn("Sentinel unexpected exception in asyncEntryInternal", e1); asyncEntry.cleanCurrentEntryInLocal(); } return asyncEntry; } private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { return asyncEntryWithPriorityInternal(resourceWrapper, count, false, args); } private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException { Context context = ContextUtil.getContext(); if (context instanceof NullContext) { // The {@link NullContext} indicates that the amount of context has exceeded the threshold, // so here init the entry only. No rule checking will be done. return new CtEntry(resourceWrapper, null, context); } if (context == null) { // Using default context. context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); } // Global switch is close, no rule checking will do. if (!Constants.ON) { return new CtEntry(resourceWrapper, null, context); } ProcessorSlot chain = lookProcessChain(resourceWrapper); /* * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, * so no rule checking will be done. */ if (chain == null) { return new CtEntry(resourceWrapper, null, context); } Entry e = new CtEntry(resourceWrapper, chain, context, count, args); try { chain.entry(context, resourceWrapper, null, count, prioritized, args); } catch (BlockException e1) { e.exit(count, args); throw e1; } catch (Throwable e1) { // This should not happen, unless there are errors existing in Sentinel internal. RecordLog.info("Sentinel unexpected exception", e1); } return e; } /** * Do all {@link Rule}s checking about the resource. * *

Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use * same {@link ProcessorSlot} globally.

* *

Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, * otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking * or exception.

* * @param resourceWrapper resource name * @param count tokens needed * @param args arguments of user method call * @return {@link Entry} represents this call * @throws BlockException if any rule's threshold is exceeded */ public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { return entryWithPriority(resourceWrapper, count, false, args); } /** * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will * be created if the resource doesn't relate one. * *

Same resource({@link ResourceWrapper#equals(Object)}) will share the same * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.

* *

* Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, * otherwise null will return. *

* * @param resourceWrapper target resource * @return {@link ProcessorSlotChain} of the resource */ ProcessorSlot lookProcessChain(ResourceWrapper resourceWrapper) { ProcessorSlotChain chain = chainMap.get(resourceWrapper); if (chain == null) { synchronized (LOCK) { chain = chainMap.get(resourceWrapper); if (chain == null) { // Entry size limit. if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { return null; } chain = SlotChainProvider.newSlotChain(); Map newMap = new HashMap( chainMap.size() + 1); newMap.putAll(chainMap); newMap.put(resourceWrapper, chain); chainMap = newMap; } } } return chain; } /** * Get current size of created slot chains. * * @return size of created slot chains * @since 0.2.0 */ public static int entrySize() { return chainMap.size(); } /** * Reset the slot chain map. Only for internal test. * * @since 0.2.0 */ static void resetChainMap() { chainMap.clear(); } /** * Only for internal test. * * @since 0.2.0 */ static Map getChainMap() { return chainMap; } /** * This class is used for skip context name checking. */ private final static class InternalContextUtil extends ContextUtil { static Context internalEnter(String name) { return trueEnter(name, ""); } static Context internalEnter(String name, String origin) { return trueEnter(name, origin); } } @Override public Entry entry(String name) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); return entry(resource, 1, OBJECTS0); } @Override public Entry entry(Method method) throws BlockException { MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT); return entry(resource, 1, OBJECTS0); } @Override public Entry entry(Method method, EntryType type) throws BlockException { MethodResourceWrapper resource = new MethodResourceWrapper(method, type); return entry(resource, 1, OBJECTS0); } @Override public Entry entry(String name, EntryType type) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return entry(resource, 1, OBJECTS0); } @Override public Entry entry(Method method, EntryType type, int count) throws BlockException { MethodResourceWrapper resource = new MethodResourceWrapper(method, type); return entry(resource, count, OBJECTS0); } @Override public Entry entry(String name, EntryType type, int count) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return entry(resource, count, OBJECTS0); } @Override public Entry entry(Method method, int count) throws BlockException { MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT); return entry(resource, count, OBJECTS0); } @Override public Entry entry(String name, int count) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); return entry(resource, count, OBJECTS0); } @Override public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException { MethodResourceWrapper resource = new MethodResourceWrapper(method, type); return entry(resource, count, args); } @Override public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return entry(resource, count, args); } @Override public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return asyncEntryInternal(resource, count, args); } @Override public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return entryWithPriority(resource, count, prioritized); } @Override public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized, Object... args) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, type); return entryWithPriority(resource, count, prioritized, args); } @Override public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args) throws BlockException { return entryWithType(name, resourceType, entryType, count, false, args); } @Override public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, Object[] args) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType); return entryWithPriority(resource, count, prioritized, args); } @Override public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, Object[] args) throws BlockException { StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType); return asyncEntryWithPriorityInternal(resource, count, prioritized, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.util.function.BiConsumer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.context.Context; /** * Each {@link SphU}#entry() will return an {@link Entry}. This class holds information of current invocation:
* *
    *
  • createTime, the create time of this entry, using for rt statistics.
  • *
  • current {@link Node}, that is statistics of the resource in current context.
  • *
  • origin {@link Node}, that is statistics for the specific origin. Usually the * origin could be the Service Consumer's app name, see * {@link ContextUtil#enter(String name, String origin)}
  • *
  • {@link ResourceWrapper}, that is resource name.
  • *
    *
* *

* A invocation tree will be created if we invoke SphU#entry() multi times in the same {@link Context}, * so parent or child entry may be held by this to form the tree. Since {@link Context} always holds * the current entry in the invocation tree, every {@link Entry#exit()} call should modify * {@link Context#setCurEntry(Entry)} as parent entry of this. *

* * @author qinan.qn * @author jialiang.linjl * @author leyou(lihao) * @author Eric Zhao * @see SphU * @see Context * @see ContextUtil */ public abstract class Entry implements AutoCloseable { protected static final Object[] OBJECTS0 = new Object[0]; private final long createTimestamp; private long completeTimestamp; private Node curNode; /** * {@link Node} of the specific origin, Usually the origin is the Service Consumer. */ private Node originNode; private Throwable error; private BlockException blockError; protected final ResourceWrapper resourceWrapper; protected final int count; protected final Object[] args; public Entry(ResourceWrapper resourceWrapper) { this(resourceWrapper, 1, OBJECTS0); } public Entry(ResourceWrapper resourceWrapper, int count, Object[] args) { this.resourceWrapper = resourceWrapper; this.createTimestamp = TimeUtil.currentTimeMillis(); this.count = count; this.args = args; } public ResourceWrapper getResourceWrapper() { return resourceWrapper; } /** * Complete the current resource entry and restore the entry stack in context. * Do not need to carry count or args parameter, initialization does * @throws ErrorEntryFreeException if entry in current context does not match current entry */ public void exit() throws ErrorEntryFreeException { exit(count, args); } public void exit(int count) throws ErrorEntryFreeException { exit(count, args); } /** * Equivalent to {@link #exit()}. Support try-with-resources since JDK 1.7. * * @since 1.5.0 */ @Override public void close() { exit(); } /** * Exit this entry. This method should invoke if and only if once at the end of the resource protection. * * @param count tokens to release. * @param args extra parameters * @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry. */ public abstract void exit(int count, Object... args) throws ErrorEntryFreeException; /** * Exit this entry. * * @param count tokens to release. * @param args extra parameters * @return next available entry after exit, that is the parent entry. * @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry. */ protected abstract Entry trueExit(int count, Object... args) throws ErrorEntryFreeException; /** * Get related {@link Node} of the parent {@link Entry}. * * @return */ public abstract Node getLastNode(); public long getCreateTimestamp() { return createTimestamp; } public long getCompleteTimestamp() { return completeTimestamp; } public Entry setCompleteTimestamp(long completeTimestamp) { this.completeTimestamp = completeTimestamp; return this; } public Node getCurNode() { return curNode; } public void setCurNode(Node node) { this.curNode = node; } public BlockException getBlockError() { return blockError; } public Entry setBlockError(BlockException blockError) { this.blockError = blockError; return this; } public Throwable getError() { return error; } public void setError(Throwable error) { this.error = error; } /** * Get origin {@link Node} of the this {@link Entry}. * * @return origin {@link Node} of the this {@link Entry}, may be null if no origin specified by * {@link ContextUtil#enter(String name, String origin)}. */ public Node getOriginNode() { return originNode; } public void setOriginNode(Node originNode) { this.originNode = originNode; } /** * Like {@code CompletableFuture} since JDK 8, it guarantees specified handler * is invoked when this entry terminated (exited), no matter it's blocked or permitted. * Use it when you did some STATEFUL operations on entries. * * @param handler handler function on the invocation terminates * @since 1.8.0 */ public abstract void whenTerminate(BiConsumer handler); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/EntryType.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; /** * An enum marks resource invocation direction. * * @author jialiang.linjl * @author Yanming Zhou */ public enum EntryType { /** * Inbound traffic */ IN, /** * Outbound traffic */ OUT; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/Env.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.init.InitExecutor; /** * Sentinel Env. This class will trigger all initialization for Sentinel. * *

* NOTE: to prevent deadlocks, other classes' static code block or static field should * NEVER refer to this class. *

* * @author jialiang.linjl */ public class Env { public static final Sph sph = new CtSph(); static { // If init fails, the process will exit. InitExecutor.doInit(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/ErrorEntryFreeException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; /** * Represents order mismatch of resource entry and resource exit (pair mismatch). * * @author qinan.qn */ public class ErrorEntryFreeException extends RuntimeException { public ErrorEntryFreeException(String s) { super(s); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/ResourceTypeConstants.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; /** * @author Eric Zhao * @since 1.7.0 */ public final class ResourceTypeConstants { public static final int COMMON = 0; public static final int COMMON_WEB = 1; public static final int COMMON_RPC = 2; public static final int COMMON_API_GATEWAY = 3; public static final int COMMON_DB_SQL = 4; private ResourceTypeConstants() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.lang.reflect.Method; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * The basic interface for recording statistics and performing rule checking for resources. * * @author qinan.qn * @author jialiang.linjl * @author leyou * @author Eric Zhao */ public interface Sph extends SphResourceTypeSupport { /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(String name) throws BlockException; /** * Record statistics and perform rule checking for the given method. * * @param method the protected method * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method) throws BlockException; /** * Record statistics and perform rule checking for the given method. * * @param method the protected method * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method, int batchCount) throws BlockException; /** * Record statistics and perform rule checking for the given resource. * * @param name the unique string for the resource * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(String name, int batchCount) throws BlockException; /** * Record statistics and perform rule checking for the given method. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method, EntryType trafficType) throws BlockException; /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(String name, EntryType trafficType) throws BlockException; /** * Record statistics and perform rule checking for the given method. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException; /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException; /** * Record statistics and perform rule checking for the given resource. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args parameters of the method for flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** * Create a protected asynchronous resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return created asynchronous entry * @throws BlockException if the block criteria is met * @since 0.2.0 */ AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** * Create a protected resource with priority. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met * @since 1.4.0 */ Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized) throws BlockException; /** * Create a protected resource with priority. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met * @since 1.5.0 */ Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized, Object... args) throws BlockException; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.lang.reflect.Method; import java.util.List; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; /** * Conceptually, physical or logical resource that need protection should be * surrounded by an entry. The requests to this resource will be blocked if any * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked, * {@link SphO}#entry() will return false. * *

* To configure the criteria, we can use XXXRuleManager.loadRules() to add rules. eg. * {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)}, * {@link SystemRuleManager#loadRules(List)}. *

* *

* Following code is an example. {@code "abc"} represent a unique name for the * protected resource: *

* *
 * public void foo() {
 *    if (SphO.entry("abc")) {
 *        try {
 *            // business logic
 *        } finally {
 *            SphO.exit(); // must exit()
 *        }
 *    } else {
 *        // failed to enter the protected resource.
 *    }
 * }
 * 
* * Make sure {@code SphO.entry()} and {@link SphO#exit()} be paired in the same thread, * otherwise {@link ErrorEntryFreeException} will be thrown. * * @author jialiang.linjl * @author leyou * @author Eric Zhao * @see SphU */ public class SphO { private static final Object[] OBJECTS0 = new Object[0]; /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name) { return entry(name, EntryType.OUT, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method) { return entry(method, EntryType.OUT, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method, int batchCount) { return entry(method, EntryType.OUT, batchCount, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique string for the resource * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name, int batchCount) { return entry(name, EntryType.OUT, batchCount, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method, EntryType type) { return entry(method, type, 1, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name, EntryType type) { return entry(name, type, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method, EntryType type, int count) { return entry(method, type, count, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name, EntryType type, int count) { return entry(name, type, count, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name, EntryType trafficType, int batchCount, Object... args) { try { Env.sph.entry(name, trafficType, batchCount, args); } catch (BlockException e) { return false; } catch (Throwable e) { RecordLog.warn("SphO fatal error", e); return true; } return true; } /** * Record statistics and perform rule checking for the given method resource. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method, EntryType trafficType, int batchCount, Object... args) { try { Env.sph.entry(method, trafficType, batchCount, args); } catch (BlockException e) { return false; } catch (Throwable e) { RecordLog.warn("SphO fatal error", e); return true; } return true; } public static void exit(int count, Object... args) { ContextUtil.getContext().getCurEntry().exit(count, args); } public static void exit(int count) { ContextUtil.getContext().getCurEntry().exit(count, OBJECTS0); } public static void exit() { ContextUtil.getContext().getCurEntry().exit(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * @author Eric Zhao * @since 1.7.0 */ public interface SphResourceTypeSupport { /** * Record statistics and perform rule checking for the given resource with provided classification. * * @param name the unique name of the protected resource * @param resourceType the classification of the resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args) throws BlockException; /** * Record statistics and perform rule checking for the given resource with the provided classification. * * @param name the unique name of the protected resource * @param resourceType classification of the resource (e.g. Web or RPC) * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, boolean prioritized, Object[] args) throws BlockException; /** * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param resourceType classification of the resource (e.g. Web or RPC) * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType trafficType, int batchCount, boolean prioritized, Object[] args) throws BlockException; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.lang.reflect.Method; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** *

The fundamental Sentinel API for recording statistics and performing rule checking for resources.

*

* Conceptually, physical or logical resource that need protection should be * surrounded by an entry. The requests to this resource will be blocked if any * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked, * a {@link BlockException} will be thrown. *

*

* To configure the criteria, we can use XxxRuleManager.loadRules() to load rules. *

* *

* Following code is an example, {@code "abc"} represent a unique name for the * protected resource: *

* *
 *  public void foo() {
 *     Entry entry = null;
 *     try {
 *        entry = SphU.entry("abc");
 *        // resource that need protection
 *     } catch (BlockException blockException) {
 *         // when goes there, it is blocked
 *         // add blocked handle logic here
 *     } catch (Throwable bizException) {
 *         // business exception
 *         Tracer.trace(bizException);
 *     } finally {
 *         // ensure finally be executed
 *         if (entry != null){
 *             entry.exit();
 *         }
 *     }
 *  }
 * 
* *

* Make sure {@code SphU.entry()} and {@link Entry#exit()} be paired in the same thread, * otherwise {@link ErrorEntryFreeException} will be thrown. *

* * @author jialiang.linjl * @author Eric Zhao * @see SphO */ public class SphU { private static final Object[] OBJECTS0 = new Object[0]; private SphU() {} /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name) throws BlockException { return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method) throws BlockException { return Env.sph.entry(method, EntryType.OUT, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method, int batchCount) throws BlockException { return Env.sph.entry(method, EntryType.OUT, batchCount, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique string for the resource * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name, int batchCount) throws BlockException { return Env.sph.entry(name, EntryType.OUT, batchCount, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method, EntryType trafficType) throws BlockException { return Env.sph.entry(method, trafficType, 1, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name, EntryType trafficType) throws BlockException { return Env.sph.entry(name, trafficType, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException { return Env.sph.entry(method, trafficType, batchCount, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException { return Env.sph.entry(name, trafficType, batchCount, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * * @param method the protected method * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) throws BlockException { return Env.sph.entry(method, trafficType, batchCount, args); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException { return Env.sph.entry(name, trafficType, batchCount, args); } /** * Record statistics and check all rules of the resource that indicates an async invocation. * * @param name the unique name of the protected resource * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ public static AsyncEntry asyncEntry(String name) throws BlockException { return Env.sph.asyncEntry(name, EntryType.OUT, 1, OBJECTS0); } /** * Record statistics and check all rules of the resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ public static AsyncEntry asyncEntry(String name, EntryType trafficType) throws BlockException { return Env.sph.asyncEntry(name, trafficType, 1, OBJECTS0); } /** * Record statistics and check all rules of the resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ public static AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException { return Env.sph.asyncEntry(name, trafficType, batchCount, args); } /** * Record statistics and perform rule checking for the given resource. The entry is prioritized. * * @param name the unique name for the protected resource * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.4.0 */ public static Entry entryWithPriority(String name) throws BlockException { return Env.sph.entryWithPriority(name, EntryType.OUT, 1, true); } /** * Record statistics and perform rule checking for the given resource. The entry is prioritized. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.4.0 */ public static Entry entryWithPriority(String name, EntryType trafficType) throws BlockException { return Env.sph.entryWithPriority(name, trafficType, 1, true); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param resourceType classification of the resource (e.g. Web or RPC) * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ public static Entry entry(String name, int resourceType, EntryType trafficType) throws BlockException { return Env.sph.entryWithType(name, resourceType, trafficType, 1, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args) throws BlockException { return Env.sph.entryWithType(name, resourceType, trafficType, 1, args); } /** * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType) throws BlockException { return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, OBJECTS0); } /** * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, Object[] args) throws BlockException { return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, args); } /** * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param args args for parameter flow control or customized slots * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args) throws BlockException { return Env.sph.asyncEntryWithType(name, resourceType, trafficType, batchCount, false, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; /** * This class is used to record other exceptions except block exception. * * @author jialiang.linjl * @author Eric Zhao */ public class Tracer { protected static Class[] traceClasses; protected static Class[] ignoreClasses; protected static Predicate exceptionPredicate; protected Tracer() {} /** * Trace provided {@link Throwable} to the resource entry in current context. * * @param e exception to record */ public static void trace(Throwable e) { traceContext(e, ContextUtil.getContext()); } /** * Trace provided {@link Throwable} to current entry in current context. * * @param e exception to record * @param count exception count to add */ @Deprecated public static void trace(Throwable e, int count) { traceContext(e, count, ContextUtil.getContext()); } /** * Trace provided {@link Throwable} to current entry of given entrance context. * * @param e exception to record * @param context target entrance context * @since 1.8.0 */ public static void traceContext(Throwable e, Context context) { if (!shouldTrace(e)) { return; } if (context == null || context instanceof NullContext) { return; } traceEntryInternal(e, context.getCurEntry()); } /** * Trace provided {@link Throwable} and add exception count to current entry in provided context. * * @param e exception to record * @param count exception count to add * @since 1.4.2 */ @Deprecated public static void traceContext(Throwable e, int count, Context context) { if (!shouldTrace(e)) { return; } if (context == null || context instanceof NullContext) { return; } traceEntryInternal(e, context.getCurEntry()); } /** * Trace provided {@link Throwable} to the given resource entry. * * @param e exception to record * @since 1.4.2 */ public static void traceEntry(Throwable e, Entry entry) { if (!shouldTrace(e)) { return; } traceEntryInternal(e, entry); } private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) { if (entry == null) { return; } entry.setError(e); } /** * Set exception to trace. If not set, all Exception except for {@link BlockException} will be traced. *

* Note that if both {@link #setExceptionsToIgnore(Class[])} and this method is set, * the ExceptionsToIgnore will be of higher precedence. *

* * @param traceClasses the list of exception classes to trace. * @since 1.6.1 */ @SafeVarargs public static void setExceptionsToTrace(Class... traceClasses) { checkNotNull(traceClasses); Tracer.traceClasses = traceClasses; } /** * Get exception classes to trace. * * @return an array of exception classes to trace. * @since 1.6.1 */ public static Class[] getExceptionsToTrace() { return traceClasses; } /** * Set exceptions to ignore. if not set, all Exception except for {@link BlockException} will be traced. *

* Note that if both {@link #setExceptionsToTrace(Class[])} and this method is set, * the ExceptionsToIgnore will be of higher precedence. *

* * @param ignoreClasses the list of exception classes to ignore. * @since 1.6.1 */ @SafeVarargs public static void setExceptionsToIgnore(Class... ignoreClasses) { checkNotNull(ignoreClasses); Tracer.ignoreClasses = ignoreClasses; } /** * Get exception classes to ignore. * * @return an array of exception classes to ignore. * @since 1.6.1 */ public static Class[] getExceptionsToIgnore() { return ignoreClasses; } /** * Get exception predicate * @return the exception predicate. */ public static Predicate getExceptionPredicate() { return exceptionPredicate; } /** * set an exception predicate which indicates the exception should be traced(return true) or ignored(return false) * except for {@link BlockException} * @param exceptionPredicate the exception predicate */ public static void setExceptionPredicate(Predicate exceptionPredicate) { AssertUtil.notNull(exceptionPredicate, "exception predicate must not be null"); Tracer.exceptionPredicate = exceptionPredicate; } private static void checkNotNull(Class[] classes) { AssertUtil.notNull(classes, "trace or ignore classes must not be null"); for (Class clazz : classes) { AssertUtil.notNull(clazz, "trace or ignore classes must not be null"); } } /** * Check whether the throwable should be traced. * * @param t the throwable to check. * @return true if the throwable should be traced, else return false. */ protected static boolean shouldTrace(Throwable t) { if (t == null || t instanceof BlockException) { return false; } if (exceptionPredicate != null) { return exceptionPredicate.test(t); } if (ignoreClasses != null) { for (Class clazz : ignoreClasses) { if (clazz != null && clazz.isAssignableFrom(t.getClass())) { return false; } } } if (traceClasses != null) { for (Class clazz : traceClasses) { if (clazz != null && clazz.isAssignableFrom(t.getClass())) { return true; } } return false; } return true; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation; import com.alibaba.csp.sentinel.EntryType; import java.lang.annotation.*; /** * The annotation indicates a definition of Sentinel resource. * * @author Eric Zhao * @author zhaoyuguang * @since 0.1.1 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface SentinelResource { /** * @return name of the Sentinel resource */ String value() default ""; /** * @return the entry type (inbound or outbound), outbound by default */ EntryType entryType() default EntryType.OUT; /** * @return the classification (type) of the resource * @since 1.7.0 */ int resourceType() default 0; /** * @return name of the block exception function, empty by default */ String blockHandler() default ""; /** * The {@code blockHandler} is located in the same class with the original method by default. * However, if some methods share the same signature and intend to set the same block handler, * then users can set the class where the block handler exists. Note that the block handler method * must be static. * * @return the class where the block handler exists, should not provide more than one classes */ Class[] blockHandlerClass() default {}; /** * @return name of the fallback function, empty by default */ String fallback() default ""; /** * The {@code defaultFallback} is used as the default universal fallback method. * It should not accept any parameters, and the return type should be compatible * with the original method. * * @return name of the default fallback method, empty by default * @since 1.6.0 */ String defaultFallback() default ""; /** * The {@code fallback} is located in the same class with the original method by default. * However, if some methods share the same signature and intend to set the same fallback, * then users can set the class where the fallback function exists. Note that the shared fallback method * must be static. * * @return the class where the fallback method is located (only single class) * @since 1.6.0 */ Class[] fallbackClass() default {}; /** * @return the list of exception classes to trace, {@link Throwable} by default * @since 1.5.1 */ Class[] exceptionsToTrace() default {Throwable.class}; /** * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore} * will be of higher precedence. * * @return the list of exception classes to ignore, empty by default * @since 1.6.0 */ Class[] exceptionsToIgnore() default {}; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.util.TimeUtil; /** *

* Global state manager for Sentinel cluster. * This enables switching between cluster token client and server mode. *

* * @author Eric Zhao * @since 1.4.0 */ public final class ClusterStateManager { public static final int CLUSTER_CLIENT = 0; public static final int CLUSTER_SERVER = 1; public static final int CLUSTER_NOT_STARTED = -1; private static volatile int mode = CLUSTER_NOT_STARTED; private static volatile long lastModified = -1; private static volatile SentinelProperty stateProperty = new DynamicSentinelProperty(); private static final PropertyListener PROPERTY_LISTENER = new ClusterStatePropertyListener(); static { InitExecutor.doInit(); stateProperty.addListener(PROPERTY_LISTENER); } public static void registerProperty(SentinelProperty property) { synchronized (PROPERTY_LISTENER) { RecordLog.info("[ClusterStateManager] Registering new property to cluster state manager"); stateProperty.removeListener(PROPERTY_LISTENER); property.addListener(PROPERTY_LISTENER); stateProperty = property; } } public static int getMode() { return mode; } public static boolean isClient() { return mode == CLUSTER_CLIENT; } public static boolean isServer() { return mode == CLUSTER_SERVER; } /** *

* Set current mode to client mode. If Sentinel currently works in server mode, * it will be turned off. Then the cluster client will be started. *

*/ public static boolean setToClient() { if (mode == CLUSTER_CLIENT) { return true; } mode = CLUSTER_CLIENT; sleepIfNeeded(); lastModified = TimeUtil.currentTimeMillis(); return startClient(); } private static boolean startClient() { try { EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); if (server != null) { server.stop(); } ClusterTokenClient tokenClient = TokenClientProvider.getClient(); if (tokenClient != null) { tokenClient.start(); RecordLog.info("[ClusterStateManager] Changing cluster mode to client"); return true; } else { RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)"); return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex); return false; } } private static boolean stopClient() { try { ClusterTokenClient tokenClient = TokenClientProvider.getClient(); if (tokenClient != null) { tokenClient.stop(); RecordLog.info("[ClusterStateManager] Stopping the cluster token client"); return true; } else { RecordLog.warn("[ClusterStateManager] Cannot stop cluster token client (no server SPI found)"); return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when stopping cluster token client", ex); return false; } } /** *

* Set current mode to server mode. If Sentinel currently works in client mode, * it will be turned off. Then the cluster server will be started. *

*/ public static boolean setToServer() { if (mode == CLUSTER_SERVER) { return true; } mode = CLUSTER_SERVER; sleepIfNeeded(); lastModified = TimeUtil.currentTimeMillis(); return startServer(); } private static boolean startServer() { try { ClusterTokenClient tokenClient = TokenClientProvider.getClient(); if (tokenClient != null) { tokenClient.stop(); } EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); if (server != null) { server.start(); RecordLog.info("[ClusterStateManager] Changing cluster mode to server"); return true; } else { RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)"); return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex); return false; } } private static boolean stopServer() { try { EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); if (server != null) { server.stop(); RecordLog.info("[ClusterStateManager] Stopping the cluster server"); return true; } else { RecordLog.warn("[ClusterStateManager] Cannot stop server (no server SPI found)"); return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when stopping server", ex); return false; } } /** * The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). * Or we need to wait for a while. */ private static void sleepIfNeeded() { if (lastModified <= 0) { return; } long now = TimeUtil.currentTimeMillis(); long durationPast = now - lastModified; long estimated = durationPast - MIN_INTERVAL; if (estimated < 0) { try { Thread.sleep(-estimated); } catch (InterruptedException e) { e.printStackTrace(); } } } public static long getLastModified() { return lastModified; } private static class ClusterStatePropertyListener implements PropertyListener { @Override public synchronized void configLoad(Integer value) { applyStateInternal(value); } @Override public synchronized void configUpdate(Integer value) { applyStateInternal(value); } } private static boolean applyStateInternal(Integer state) { if (state == null || state < CLUSTER_NOT_STARTED) { return false; } if (state == mode) { return true; } try { switch (state) { case CLUSTER_CLIENT: return setToClient(); case CLUSTER_SERVER: return setToServer(); case CLUSTER_NOT_STARTED: setStop(); return true; default: RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state); return false; } } catch (Throwable t) { RecordLog.warn("[ClusterStateManager] Fatal error when applying state: " + state, t); return false; } } private static void setStop() { if (mode == CLUSTER_NOT_STARTED) { return; } RecordLog.info("[ClusterStateManager] Changing cluster mode to not-started"); mode = CLUSTER_NOT_STARTED; sleepIfNeeded(); lastModified = TimeUtil.currentTimeMillis(); stopClient(); stopServer(); } /** * Apply given state to cluster mode. * * @param state valid state to apply */ public static void applyState(Integer state) { stateProperty.updateValue(state); } public static void markToServer() { mode = CLUSTER_SERVER; } private static final int MIN_INTERVAL = 5 * 1000; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import java.util.Map; /** * Result entity of acquiring cluster flow token. * * @author Eric Zhao * @since 1.4.0 */ public class TokenResult { private Integer status; private int remaining; private int waitInMs; private long tokenId; private Map attachments; public TokenResult() { } public TokenResult(Integer status) { this.status = status; } public long getTokenId() { return tokenId; } public void setTokenId(long tokenId) { this.tokenId = tokenId; } public Integer getStatus() { return status; } public TokenResult setStatus(Integer status) { this.status = status; return this; } public int getRemaining() { return remaining; } public TokenResult setRemaining(int remaining) { this.remaining = remaining; return this; } public int getWaitInMs() { return waitInMs; } public TokenResult setWaitInMs(int waitInMs) { this.waitInMs = waitInMs; return this; } public Map getAttachments() { return attachments; } public TokenResult setAttachments(Map attachments) { this.attachments = attachments; return this; } @Override public String toString() { return "TokenResult{" + "status=" + status + ", remaining=" + remaining + ", waitInMs=" + waitInMs + ", attachments=" + attachments + ", tokenId=" + tokenId + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; /** * @author Eric Zhao * @since 1.4.0 */ public final class TokenResultStatus { /** * Bad client request. */ public static final int BAD_REQUEST = -4; /** * Too many request in server. */ public static final int TOO_MANY_REQUEST = -2; /** * Server or client unexpected failure (due to transport or serialization failure). */ public static final int FAIL = -1; /** * Token acquired. */ public static final int OK = 0; /** * Token acquire failed (blocked). */ public static final int BLOCKED = 1; /** * Should wait for next buckets. */ public static final int SHOULD_WAIT = 2; /** * Token acquire failed (no rule exists). */ public static final int NO_RULE_EXISTS = 3; /** * Token acquire failed (reference resource is not available). */ public static final int NO_REF_RULE_EXISTS = 4; /** * Token acquire failed (strategy not available). */ public static final int NOT_AVAILABLE = 5; /** * Token is successfully released. */ public static final int RELEASE_OK = 6; /** * Token already is released before the request arrives. */ public static final int ALREADY_RELEASE=7; private TokenResultStatus() { } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; /** * A simple descriptor for Sentinel token server. * * @author Eric Zhao * @since 1.4.0 */ public class TokenServerDescriptor { private final String host; private final int port; private String type = "default"; public TokenServerDescriptor(String host, int port) { this.host = host; this.port = port; } public String getHost() { return host; } public int getPort() { return port; } public String getType() { return type; } public TokenServerDescriptor setType(String type) { this.type = type; return this; } @Override public String toString() { return "TokenServerDescriptor{" + "host='" + host + '\'' + ", port=" + port + ", type='" + type + '\'' + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster; import java.util.Collection; /** * Service interface of flow control. * * @author Eric Zhao * @since 1.4.0 */ public interface TokenService { /** * Request tokens from remote token server. * * @param ruleId the unique rule ID * @param acquireCount token count to acquire * @param prioritized whether the request is prioritized * @return result of the token request */ TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized); /** * Request tokens for a specific parameter from remote token server. * * @param ruleId the unique rule ID * @param acquireCount token count to acquire * @param params parameter list * @return result of the token request */ TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params); /** * Request acquire concurrent tokens from remote token server. * * @param clientAddress the address of the request belong. * @param ruleId ruleId the unique rule ID * @param acquireCount token count to acquire * @return result of the token request */ TokenResult requestConcurrentToken(String clientAddress,Long ruleId,int acquireCount); /** * Request release concurrent tokens from remote token server asynchronously. * * @param tokenId the unique token ID */ void releaseConcurrentToken(Long tokenId); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client; import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; import com.alibaba.csp.sentinel.cluster.TokenService; /** * Token client interface for distributed flow control. * * @author Eric Zhao * @since 1.4.0 */ public interface ClusterTokenClient extends TokenService { /** * Get descriptor of current token server. * * @return current token server if connected, otherwise null */ TokenServerDescriptor currentServer(); /** * Start the token client. * * @throws Exception some error occurs */ void start() throws Exception; /** * Stop the token client. * * @throws Exception some error occurs */ void stop() throws Exception; /** * Get state of the cluster token client. * * @return state of the cluster token client */ int getState(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.client; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * Provider for a universal {@link ClusterTokenClient} instance. * * @author Eric Zhao * @since 1.4.0 */ public final class TokenClientProvider { private static ClusterTokenClient client = null; static { // Not strictly thread-safe, but it's OK since it will be resolved only once. resolveTokenClientInstance(); } public static ClusterTokenClient getClient() { return client; } private static void resolveTokenClientInstance() { ClusterTokenClient resolvedClient = SpiLoader.of(ClusterTokenClient.class).loadFirstInstance(); if (resolvedClient == null) { RecordLog.info( "[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); } else { client = resolvedClient; RecordLog.info("[TokenClientProvider] Cluster token client resolved: {}", client.getClass().getCanonicalName()); } } public static boolean isClientSpiAvailable() { return getClient() != null; } private TokenClientProvider() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.log; import com.alibaba.csp.sentinel.eagleeye.EagleEye; import com.alibaba.csp.sentinel.eagleeye.StatLogger; import com.alibaba.csp.sentinel.log.LogBase; /** * @author jialiang.linjl * @author Eric Zhao * @since 1.4.0 */ public final class ClusterClientStatLogUtil { private static final String FILE_NAME = "sentinel-cluster-client.log"; private static StatLogger statLogger; static { String path = LogBase.getLogBaseDir() + FILE_NAME; statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-client-record") .intervalSeconds(1) .entryDelimiter('|') .keyDelimiter(',') .valueDelimiter(',') .maxEntryCount(5000) .configLogFilePath(path) .maxFileSizeMB(300) .maxBackupIndex(3) .buildSingleton(); } public static void log(String msg) { statLogger.stat(msg).count(); } public static void log(String msg, int count) { statLogger.stat(msg).count(count); } private ClusterClientStatLogUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.log; import java.io.File; import com.alibaba.csp.sentinel.eagleeye.EagleEye; import com.alibaba.csp.sentinel.eagleeye.StatLogger; import com.alibaba.csp.sentinel.log.LogBase; /** * @author jialiang.linjl * @author Eric Zhao * @since 1.4.0 */ public final class ClusterStatLogUtil { private static final String FILE_NAME = "sentinel-cluster.log"; private static StatLogger statLogger; static { String path = LogBase.getLogBaseDir() + FILE_NAME; statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record") .intervalSeconds(1) .entryDelimiter('|') .keyDelimiter(',') .valueDelimiter(',') .maxEntryCount(5000) .configLogFilePath(path) .maxFileSizeMB(300) .maxBackupIndex(3) .buildSingleton(); } public static void log(String msg) { statLogger.stat(msg).count(); } public static void log(String msg, int count) { statLogger.stat(msg).count(count); } private ClusterStatLogUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; /** * Token server interface for distributed flow control. * * @author Eric Zhao * @since 1.4.0 */ public interface ClusterTokenServer { /** * Start the Sentinel cluster server. * * @throws Exception if any error occurs */ void start() throws Exception; /** * Stop the Sentinel cluster server. * * @throws Exception if any error occurs */ void stop() throws Exception; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import com.alibaba.csp.sentinel.cluster.TokenService; /** * Embedded token server interface that can work in embedded mode. * * @author Eric Zhao * @since 1.4.0 */ public interface EmbeddedClusterTokenServer extends ClusterTokenServer, TokenService { } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.cluster.server; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * @author Eric Zhao * @since 1.4.0 */ public final class EmbeddedClusterTokenServerProvider { private static EmbeddedClusterTokenServer server = null; static { resolveInstance(); } private static void resolveInstance() { EmbeddedClusterTokenServer s = SpiLoader.of(EmbeddedClusterTokenServer.class).loadFirstInstance(); if (s == null) { RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); } else { server = s; RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: {}", server.getClass().getCanonicalName()); } } public static EmbeddedClusterTokenServer getServer() { return server; } public static boolean isServerSpiAvailable() { return getServer() != null; } private EmbeddedClusterTokenServerProvider() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.concurrent; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Wrapped thread factory for better use. */ public class NamedThreadFactory implements ThreadFactory { private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; private final boolean daemon; public NamedThreadFactory(String namePrefix, boolean daemon) { this.daemon = daemon; SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.namePrefix = namePrefix; } public NamedThreadFactory(String namePrefix) { this(namePrefix, false); } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0); t.setDaemon(daemon); return t; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.config; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.io.File; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; /** * The universal local configuration center of Sentinel. The config is retrieved from command line arguments * and customized properties file by default. * * @author leyou * @author Eric Zhao * @author Lin Liang */ public final class SentinelConfig { /** * The default application type. * * @since 1.6.0 */ public static final int APP_TYPE_COMMON = 0; /** * Parameter value for using context classloader. */ private static final String CLASSLOADER_CONTEXT = "context"; private static final Map props = new ConcurrentHashMap<>(); private static int appType = APP_TYPE_COMMON; private static String appName = ""; public static final String PROJECT_NAME_PROP_KEY = "project.name"; public static final String APP_NAME_PROP_KEY = "csp.sentinel.app.name"; public static final String APP_TYPE_PROP_KEY = "csp.sentinel.app.type"; public static final String CHARSET = "csp.sentinel.charset"; public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size"; public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count"; public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor"; public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt"; public static final String SPI_CLASSLOADER = "csp.sentinel.spi.classloader"; public static final String METRIC_FLUSH_INTERVAL = "csp.sentinel.metric.flush.interval"; public static final String SKIP_REGEX_IF_SIMPLE_RULE_MATCHED_KEY = "csp.sentinel.rule.regex.skip.if.simple.matched"; public static final String DEFAULT_CHARSET = "UTF-8"; public static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50; public static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6; public static final int DEFAULT_COLD_FACTOR = 3; public static final int DEFAULT_STATISTIC_MAX_RT = 5000; public static final long DEFAULT_METRIC_FLUSH_INTERVAL = 1L; public static final String DEFAULT_SKIP_REGEX_IF_SIMPLE_RULE_MATCHED = "false"; static { try { initialize(); loadProps(); resolveAppName(); resolveAppType(); RecordLog.info("[SentinelConfig] Application type resolved: {}", appType); } catch (Throwable ex) { RecordLog.warn("[SentinelConfig] Failed to initialize", ex); ex.printStackTrace(); } } private static void resolveAppType() { try { String type = getConfig(APP_TYPE_PROP_KEY); if (type == null) { appType = APP_TYPE_COMMON; return; } appType = Integer.parseInt(type); if (appType < 0) { appType = APP_TYPE_COMMON; } } catch (Exception ex) { appType = APP_TYPE_COMMON; } } private static void initialize() { // Init default properties. setConfig(CHARSET, DEFAULT_CHARSET); setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE)); setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT)); setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR)); setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT)); setConfig(METRIC_FLUSH_INTERVAL, String.valueOf(DEFAULT_METRIC_FLUSH_INTERVAL)); setConfig(SKIP_REGEX_IF_SIMPLE_RULE_MATCHED_KEY, DEFAULT_SKIP_REGEX_IF_SIMPLE_RULE_MATCHED); } private static void loadProps() { Properties properties = SentinelConfigLoader.getProperties(); for (Object key : properties.keySet()) { setConfig((String) key, (String) properties.get(key)); } } /** * Get config value of the specific key. * * @param key config key * @return the config value. */ public static String getConfig(String key) { AssertUtil.notNull(key, "key cannot be null"); return props.get(key); } /** * Get config value of the specific key. * * @param key config key * @param envVariableKey Get the value of the environment variable with the given key * @return the config value. */ public static String getConfig(String key, boolean envVariableKey) { AssertUtil.notNull(key, "key cannot be null"); if (envVariableKey) { String value = System.getenv(key); if (StringUtil.isNotEmpty(value)) { return value; } } return getConfig(key); } public static void setConfig(String key, String value) { AssertUtil.notNull(key, "key cannot be null"); AssertUtil.notNull(value, "value cannot be null"); props.put(key, value); } public static String removeConfig(String key) { AssertUtil.notNull(key, "key cannot be null"); return props.remove(key); } public static void setConfigIfAbsent(String key, String value) { AssertUtil.notNull(key, "key cannot be null"); AssertUtil.notNull(value, "value cannot be null"); String v = props.get(key); if (v == null) { props.put(key, value); } } public static String getAppName() { return appName; } /** * Get application type. * * @return application type, common (0) by default * @since 1.6.0 */ public static int getAppType() { return appType; } public static String charset() { return props.get(CHARSET); } /** * Get the metric log flush interval in second * @return the metric log flush interval in second * @since 1.8.1 */ public static long metricLogFlushIntervalSec() { String flushIntervalStr = SentinelConfig.getConfig(METRIC_FLUSH_INTERVAL); if (flushIntervalStr == null) { return DEFAULT_METRIC_FLUSH_INTERVAL; } try { return Long.parseLong(flushIntervalStr); } catch (Throwable throwable) { RecordLog.warn("[SentinelConfig] Parse the metricLogFlushInterval fail, use default value: " + DEFAULT_METRIC_FLUSH_INTERVAL, throwable); return DEFAULT_METRIC_FLUSH_INTERVAL; } } public static long singleMetricFileSize() { try { return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE)); } catch (Throwable throwable) { RecordLog.warn("[SentinelConfig] Parse singleMetricFileSize fail, use default value: " + DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable); return DEFAULT_SINGLE_METRIC_FILE_SIZE; } } public static int totalMetricFileCount() { try { return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT)); } catch (Throwable throwable) { RecordLog.warn("[SentinelConfig] Parse totalMetricFileCount fail, use default value: " + DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable); return DEFAULT_TOTAL_METRIC_FILE_COUNT; } } public static int coldFactor() { try { int coldFactor = Integer.parseInt(props.get(COLD_FACTOR)); // check the cold factor larger than 1 if (coldFactor <= 1) { coldFactor = DEFAULT_COLD_FACTOR; RecordLog.warn("cold factor=" + coldFactor + ", should be larger than 1, use default value: " + DEFAULT_COLD_FACTOR); } return coldFactor; } catch (Throwable throwable) { RecordLog.warn("[SentinelConfig] Parse coldFactor fail, use default value: " + DEFAULT_COLD_FACTOR, throwable); return DEFAULT_COLD_FACTOR; } } /** *

Get the max RT value that Sentinel could accept for system BBR strategy.

* * @return the max allowed RT value * @since 1.4.1 */ public static int statisticMaxRt() { String v = props.get(STATISTIC_MAX_RT); try { if (StringUtil.isEmpty(v)) { return DEFAULT_STATISTIC_MAX_RT; } return Integer.parseInt(v); } catch (Throwable throwable) { RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {}, using the default value instead: " + DEFAULT_STATISTIC_MAX_RT, v, throwable); SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT)); return DEFAULT_STATISTIC_MAX_RT; } } /** * Function for resolving project name. The order is elaborated below: * *
    *
  1. Resolve the value from {@code CSP_SENTINEL_APP_NAME} system environment;
  2. *
  3. Resolve the value from {@code csp.sentinel.app.name} system property;
  4. *
  5. Resolve the value from {@code project.name} system property (for compatibility);
  6. *
  7. Resolve the value from {@code sun.java.command} system property, then remove path, arguments and ".jar" or ".JAR" * suffix, use the result as app name. Note that whitespace in file name or path is not allowed, or a * wrong app name may be gotten, For example: *

    * * "test.Main" -> test.Main
    * "/target/test.Main" -> test.Main
    * "/target/test.Main args1" -> test.Main
    * "Main.jar" -> Main
    * "/target/Main.JAR args1" -> Main
    * "Mai n.jar" -> Mai // whitespace in file name is not allowed
    *
    *

    *
  8. *
*/ private static void resolveAppName() { // Priority: system env -> csp.sentinel.app.name -> project.name -> main class (or jar) name String envKey = toEnvKey(APP_NAME_PROP_KEY); String n = System.getenv(envKey); if (!StringUtil.isBlank(n)) { appName = n; RecordLog.info("App name resolved from system env {}: {}", envKey, appName); return; } n = props.get(APP_NAME_PROP_KEY); if (!StringUtil.isBlank(n)) { appName = n; RecordLog.info("App name resolved from property {}: {}", APP_NAME_PROP_KEY, appName); return; } n = props.get(PROJECT_NAME_PROP_KEY); if (!StringUtil.isBlank(n)) { appName = n; RecordLog.info("App name resolved from property {}: {}", PROJECT_NAME_PROP_KEY, appName); return; } // Parse sun.java.command property by default. String command = System.getProperty("sun.java.command"); if (StringUtil.isBlank(command)) { RecordLog.warn("Cannot resolve default appName from property sun.java.command"); return; } command = command.split("\\s")[0]; String separator = File.separator; if (command.contains(separator)) { String[] strs; if ("\\".equals(separator)) { // Handle separator in Windows. strs = command.split("\\\\"); } else { strs = command.split(separator); } command = strs[strs.length - 1]; } if (command.toLowerCase().endsWith(".jar")) { command = command.substring(0, command.length() - 4); } appName = command; RecordLog.info("App name resolved from default: {}", appName); } private static String toEnvKey(/*@NotBlank*/ String propKey) { return propKey.toUpperCase().replace('.', '_'); } /** * Whether use context classloader via config parameter * * @return Whether use context classloader */ public static boolean shouldUseContextClassloader() { String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER); return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf); } /** * Return whether to skip regex matching when simple rules already matched. * Default: false (keeps backward compatibility). */ public static boolean shouldSkipRegexIfSimpleRuleMatched() { return Boolean.parseBoolean(getConfig(SKIP_REGEX_IF_SIMPLE_RULE_MATCHED_KEY)); } private SentinelConfig() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.config; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.ConfigUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.io.File; import java.util.Map; import java.util.Properties; import java.util.concurrent.CopyOnWriteArraySet; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; /** *

The loader that responsible for loading Sentinel common configurations.

* * @author lianglin * @since 1.7.0 */ public final class SentinelConfigLoader { public static final String SENTINEL_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE"; public static final String SENTINEL_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file"; private static final String DEFAULT_SENTINEL_CONFIG_FILE = "classpath:sentinel.properties"; private static Properties properties = new Properties(); static { try { load(); } catch (Throwable t) { RecordLog.warn("[SentinelConfigLoader] Failed to initialize configuration items", t); } } private static void load() { // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path String fileName = System.getProperty(SENTINEL_CONFIG_PROPERTY_KEY); if (StringUtil.isBlank(fileName)) { fileName = System.getenv(SENTINEL_CONFIG_ENV_KEY); if (StringUtil.isBlank(fileName)) { fileName = DEFAULT_SENTINEL_CONFIG_FILE; } } Properties p = ConfigUtil.loadProperties(fileName); if (p != null && !p.isEmpty()) { RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from {}", fileName); properties.putAll(p); } for (Map.Entry entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) { String configKey = entry.getKey().toString(); String newConfigValue = entry.getValue().toString(); String oldConfigValue = properties.getProperty(configKey); properties.put(configKey, newConfigValue); if (oldConfigValue != null) { RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {}: {} -> {}", configKey, oldConfigValue, newConfigValue); } } } public static Properties getProperties() { return properties; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/Context.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphO; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; /** * This class holds metadata of current invocation:
* *
    *
  • the {@link EntranceNode}: the root of the current invocation * tree.
  • *
  • the current {@link Entry}: the current invocation point.
  • *
  • the current {@link Node}: the statistics related to the * {@link Entry}.
  • *
  • the origin: The origin is useful when we want to control different * invoker/consumer separately. Usually the origin could be the Service Consumer's app name * or origin IP.
  • *
*

* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}, * if we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used. *

*

* A invocation tree will be created if we invoke {@link SphU}#entry() multi times in * the same context. *

*

* Same resource in different context will count separately, see {@link NodeSelectorSlot}. *

* * @author jialiang.linjl * @author leyou(lihao) * @author Eric Zhao * @see ContextUtil * @see NodeSelectorSlot */ public class Context { /** * Context name. */ private final String name; /** * The entrance node of current invocation tree. */ private DefaultNode entranceNode; /** * Current processing entry. */ private Entry curEntry; /** * The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP). */ private String origin = ""; private final boolean async; /** * Create a new async context. * * @param entranceNode entrance node of the context * @param name context name * @return the new created context * @since 0.2.0 */ public static Context newAsyncContext(DefaultNode entranceNode, String name) { return new Context(name, entranceNode, true); } public Context(DefaultNode entranceNode, String name) { this(name, entranceNode, false); } public Context(String name, DefaultNode entranceNode, boolean async) { this.name = name; this.entranceNode = entranceNode; this.async = async; } public boolean isAsync() { return async; } public String getName() { return name; } public Node getCurNode() { return curEntry == null ? null : curEntry.getCurNode(); } public Context setCurNode(Node node) { this.curEntry.setCurNode(node); return this; } public Entry getCurEntry() { return curEntry; } public Context setCurEntry(Entry curEntry) { this.curEntry = curEntry; return this; } public String getOrigin() { return origin; } public Context setOrigin(String origin) { this.origin = origin; return this; } public double getOriginTotalQps() { return getOriginNode() == null ? 0 : getOriginNode().totalQps(); } public double getOriginBlockQps() { return getOriginNode() == null ? 0 : getOriginNode().blockQps(); } public double getOriginPassReqQps() { return getOriginNode() == null ? 0 : getOriginNode().successQps(); } public double getOriginPassQps() { return getOriginNode() == null ? 0 : getOriginNode().passQps(); } public long getOriginTotalRequest() { return getOriginNode() == null ? 0 : getOriginNode().totalRequest(); } public long getOriginBlockRequest() { return getOriginNode() == null ? 0 : getOriginNode().blockRequest(); } public double getOriginAvgRt() { return getOriginNode() == null ? 0 : getOriginNode().avgRt(); } public int getOriginCurThreadNum() { return getOriginNode() == null ? 0 : getOriginNode().curThreadNum(); } public DefaultNode getEntranceNode() { return entranceNode; } /** * Get the parent {@link Node} of the current. * * @return the parent node of the current. */ public Node getLastNode() { if (curEntry != null && curEntry.getLastNode() != null) { return curEntry.getLastNode(); } else { return entranceNode; } } public Node getOriginNode() { return curEntry == null ? null : curEntry.getOriginNode(); } @Override public String toString() { return "Context{" + "name='" + name + '\'' + ", entranceNode=" + entranceNode + ", curEntry=" + curEntry + ", origin='" + origin + '\'' + ", async=" + async + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextNameDefineException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; /** * @author qinan.qn */ public class ContextNameDefineException extends RuntimeException { public ContextNameDefineException(String message) { super(message); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphO; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; /** * Utility class to get or create {@link Context} in current thread. * *

* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}. * If we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used. *

* * @author jialiang.linjl * @author leyou(lihao) * @author Eric Zhao */ public class ContextUtil { /** * Store the context in ThreadLocal for easy access. */ private static ThreadLocal contextHolder = new ThreadLocal<>(); /** * Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name. */ private static volatile Map contextNameNodeMap = new HashMap<>(); private static final ReentrantLock LOCK = new ReentrantLock(); private static final Context NULL_CONTEXT = new NullContext(); static { // Cache the entrance node for default context. initDefaultContext(); } private static void initDefaultContext() { String defaultContextName = Constants.CONTEXT_DEFAULT_NAME; EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null); Constants.ROOT.addChild(node); contextNameNodeMap.put(defaultContextName, node); } /** * Not thread-safe, only for test. */ static void resetContextMap() { if (contextNameNodeMap != null) { RecordLog.warn("Context map cleared and reset to initial state"); contextNameNodeMap.clear(); initDefaultContext(); } } /** *

* Enter the invocation context, which marks as the entrance of an invocation chain. * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}. * New context will be created if current thread doesn't have one. *

*

* A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node * of the invocation chain. New {@link EntranceNode} will be created if * current context does't have one. Note that same context name will share * same {@link EntranceNode} globally. *

*

* The origin node will be created in {@link com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot}. * Note that each distinct {@code origin} of different resources will lead to creating different new * {@link Node}, meaning that total amount of created origin statistic nodes will be:
* {@code distinct resource name amount * distinct origin count}.
* So when there are too many origins, memory footprint should be carefully considered. *

*

* Same resource in different context will count separately, see {@link NodeSelectorSlot}. *

* * @param name the context name * @param origin the origin of this invocation, usually the origin could be the Service * Consumer's app name. The origin is useful when we want to control different * invoker/consumer separately. * @return The invocation context of the current thread */ public static Context enter(String name, String origin) { if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) { throw new ContextNameDefineException( "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!"); } return trueEnter(name, origin); } protected static Context trueEnter(String name, String origin) { Context context = contextHolder.get(); if (context == null) { Map localCacheNameMap = contextNameNodeMap; DefaultNode node = localCacheNameMap.get(name); if (node == null) { if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { LOCK.lock(); try { node = contextNameNodeMap.get(name); if (node == null) { if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null); // Add entrance node. Constants.ROOT.addChild(node); Map newMap = new HashMap<>(contextNameNodeMap.size() + 1); newMap.putAll(contextNameNodeMap); newMap.put(name, node); contextNameNodeMap = newMap; } } } finally { LOCK.unlock(); } } } context = new Context(node, name); context.setOrigin(origin); contextHolder.set(context); } return context; } private static boolean shouldWarn = true; private static void setNullContext() { contextHolder.set(NULL_CONTEXT); // Don't need to be thread-safe. if (shouldWarn) { RecordLog.warn("[SentinelStatusChecker] WARN: Amount of context exceeds the threshold " + Constants.MAX_CONTEXT_NAME_SIZE + ". Entries in new contexts will NOT take effect!"); shouldWarn = false; } } /** *

* Enter the invocation context, which marks as the entrance of an invocation chain. * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}. * New context will be created if current thread doesn't have one. *

*

* A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node * of the invocation chain. New {@link EntranceNode} will be created if * current context does't have one. Note that same context name will share * same {@link EntranceNode} globally. *

*

* Same resource in different context will count separately, see {@link NodeSelectorSlot}. *

* * @param name the context name * @return The invocation context of the current thread */ public static Context enter(String name) { return enter(name, ""); } /** * Exit context of current thread, that is removing {@link Context} in the * ThreadLocal. */ public static void exit() { Context context = contextHolder.get(); if (context != null && context.getCurEntry() == null) { contextHolder.set(null); } } /** * Get current size of context entrance node map. * * @return current size of context entrance node map * @since 0.2.0 */ public static int contextSize() { return contextNameNodeMap.size(); } /** * Check if provided context is a default auto-created context. * * @param context context to check * @return true if it is a default context, otherwise false * @since 0.2.0 */ public static boolean isDefaultContext(Context context) { if (context == null) { return false; } return Constants.CONTEXT_DEFAULT_NAME.equals(context.getName()); } /** * Get {@link Context} of current thread. * * @return context of current thread. Null value will be return if current * thread does't have context. */ public static Context getContext() { return contextHolder.get(); } /** *

* Replace current context with the provided context. * This is mainly designed for context switching (e.g. in asynchronous invocation). *

*

* Note: When switching context manually, remember to restore the original context. * For common scenarios, you can use {@link #runOnContext(Context, Runnable)}. *

* * @param newContext new context to set * @return old context * @since 0.2.0 */ static Context replaceContext(Context newContext) { Context backupContext = contextHolder.get(); if (newContext == null) { contextHolder.remove(); } else { contextHolder.set(newContext); } return backupContext; } /** * Execute the code within provided context. * This is mainly designed for context switching (e.g. in asynchronous invocation). * * @param context the context * @param f lambda to run within the context * @since 0.2.0 */ public static void runOnContext(Context context, Runnable f) { Context curContext = replaceContext(context); try { f.run(); } finally { replaceContext(curContext); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/NullContext.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; import com.alibaba.csp.sentinel.Constants; /** * If total {@link Context} exceed {@link Constants#MAX_CONTEXT_NAME_SIZE}, a * {@link NullContext} will get when invoke {@link ContextUtil}.enter(), means * no rules checking will do. * * @author qinan.qn */ public class NullContext extends Context { public NullContext() { super(null, "null_context_internal"); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; class BaseLoggerBuilder> { protected final String loggerName; protected String filePath = null; protected long maxFileSize = 1024; protected char entryDelimiter = '|'; protected int maxBackupIndex = 3; BaseLoggerBuilder(String loggerName) { this.loggerName = loggerName; } public T logFilePath(String logFilePath) { return configLogFilePath(logFilePath, EagleEye.EAGLEEYE_LOG_DIR); } public T appFilePath(String appFilePath) { return configLogFilePath(appFilePath, EagleEye.APP_LOG_DIR); } public T baseLogFilePath(String baseLogFilePath) { return configLogFilePath(baseLogFilePath, EagleEye.BASE_LOG_DIR); } @SuppressWarnings("unchecked") private T configLogFilePath(String filePathToConfig, String basePath) { EagleEyeCoreUtils.checkNotNullEmpty(filePathToConfig, "filePath"); if (filePathToConfig.charAt(0) != '/') { filePathToConfig = basePath + filePathToConfig; } this.filePath = filePathToConfig; return (T)this; } @SuppressWarnings("unchecked") public T configLogFilePath(String filePath) { EagleEyeCoreUtils.checkNotNullEmpty(filePath, "filePath"); this.filePath = filePath; return (T)this; } @SuppressWarnings("unchecked") public T maxFileSizeMB(long maxFileSizeMB) { if (maxFileSize < 10) { throw new IllegalArgumentException("Invalid maxFileSizeMB"); } this.maxFileSize = maxFileSizeMB * 1024 * 1024; return (T)this; } @SuppressWarnings("unchecked") public T maxBackupIndex(int maxBackupIndex) { if (maxBackupIndex < 1) { throw new IllegalArgumentException(""); } this.maxBackupIndex = maxBackupIndex; return (T)this; } @SuppressWarnings("unchecked") public T entryDelimiter(char entryDelimiter) { this.entryDelimiter = entryDelimiter; return (T)this; } String getLoggerName() { return loggerName; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEye.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; public final class EagleEye { public static final String CLASS_LOCATION = getEagleEyeLocation(); static final String USER_HOME = locateUserHome(); static final String BASE_LOG_DIR = locateBaseLogPath(); static final String EAGLEEYE_LOG_DIR = locateEagleEyeLogPath(); static final String APP_LOG_DIR = locateAppLogPath(); static final Charset DEFAULT_CHARSET = getDefaultOutputCharset(); static final String EAGLEEYE_SELF_LOG_FILE = EagleEye.EAGLEEYE_LOG_DIR + "eagleeye-self.log"; // 200MB static final long MAX_SELF_LOG_FILE_SIZE = 200 * 1024 * 1024; static EagleEyeAppender selfAppender = createSelfLogger(); static private TokenBucket exceptionBucket = new TokenBucket(10, TimeUnit.SECONDS.toMillis(10)); static String getEagleEyeLocation() { try { URL resource = EagleEye.class.getProtectionDomain().getCodeSource().getLocation(); if (resource != null) { return resource.toString(); } } catch (Throwable t) { // ignore } return "unknown location"; } static Charset getDefaultOutputCharset() { Charset cs; String charsetName = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.CHARSET"); if (EagleEyeCoreUtils.isNotBlank(charsetName)) { charsetName = charsetName.trim(); try { cs = Charset.forName(charsetName); if (cs != null) { return cs; } } catch (Exception e) { // quietly } } try { cs = Charset.forName("GB18030"); } catch (Exception e) { try { cs = Charset.forName("GBK"); } catch (Exception e2) { cs = Charset.forName("UTF-8"); } } return cs; } private static String locateUserHome() { String userHome = EagleEyeCoreUtils.getSystemProperty("user.home"); if (EagleEyeCoreUtils.isNotBlank(userHome)) { if (!userHome.endsWith(File.separator)) { userHome += File.separator; } } else { userHome = "/tmp/"; } return userHome; } private static String locateBaseLogPath() { String tmpPath = EagleEyeCoreUtils.getSystemProperty("JM.LOG.PATH"); if (EagleEyeCoreUtils.isNotBlank(tmpPath)) { if (!tmpPath.endsWith(File.separator)) { tmpPath += File.separator; } } else { tmpPath = USER_HOME + "logs" + File.separator; } return tmpPath; } private static String locateEagleEyeLogPath() { String tmpPath = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.LOG.PATH"); if (EagleEyeCoreUtils.isNotBlank(tmpPath)) { if (!tmpPath.endsWith(File.separator)) { tmpPath += File.separator; } } else { tmpPath = BASE_LOG_DIR + "eagleeye" + File.separator; } return tmpPath; } private static String locateAppLogPath() { String appName = EagleEyeCoreUtils.getSystemProperty("project.name"); if (EagleEyeCoreUtils.isNotBlank(appName)) { return USER_HOME + appName + File.separator + "logs" + File.separator; } else { return EAGLEEYE_LOG_DIR; } } static private final EagleEyeAppender createSelfLogger() { EagleEyeRollingFileAppender selfAppender = new EagleEyeRollingFileAppender(EAGLEEYE_SELF_LOG_FILE, EagleEyeCoreUtils.getSystemPropertyForLong("EAGLEEYE.LOG.SELF.FILESIZE", MAX_SELF_LOG_FILE_SIZE), false); return new SyncAppender(selfAppender); } static { initEagleEye(); } private static void initEagleEye() { try { selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")" + ", classloader=" + EagleEye.class.getClassLoader()); } catch (Throwable e) { selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")"); } try { EagleEyeLogDaemon.start(); } catch (Throwable e) { selfLog("[ERROR] fail to start EagleEyeLogDaemon", e); } try { StatLogController.start(); } catch (Throwable e) { selfLog("[ERROR] fail to start StatLogController", e); } } public static void shutdown() { selfLog("[WARN] EagleEye is shutting down (" + CLASS_LOCATION + ")"); EagleEye.flush(); try { StatLogController.stop(); EagleEye.selfLog("[INFO] StatLogController stopped"); } catch (Throwable e) { selfLog("[ERROR] fail to stop StatLogController", e); } try { EagleEyeLogDaemon.stop(); EagleEye.selfLog("[INFO] EagleEyeLogDaemon stopped"); } catch (Throwable e) { selfLog("[ERROR] fail to stop EagleEyeLogDaemon", e); } EagleEye.selfLog("[WARN] EagleEye shutdown successfully (" + CLASS_LOCATION + ")"); try { selfAppender.close(); } catch (Throwable e) { // ignore } } private EagleEye() { } static public StatLogger statLogger(String loggerName) { return statLoggerBuilder(loggerName).buildSingleton(); } static public StatLoggerBuilder statLoggerBuilder(String loggerName) { return new StatLoggerBuilder(loggerName); } static void setEagleEyeSelfAppender(EagleEyeAppender appender) { selfAppender = appender; } public static void selfLog(String log) { try { String timestamp = EagleEyeCoreUtils.formatTime(System.currentTimeMillis()); String line = "[" + timestamp + "] " + log + EagleEyeCoreUtils.NEWLINE; selfAppender.append(line); } catch (Throwable t) { } } public static void selfLog(String log, Throwable e) { long now = System.currentTimeMillis(); if (exceptionBucket.accept(now)) { try { String timestamp = EagleEyeCoreUtils.formatTime(now); StringWriter sw = new StringWriter(4096); PrintWriter pw = new PrintWriter(sw, false); pw.append('[').append(timestamp).append("] ").append(log).append(EagleEyeCoreUtils.NEWLINE); e.printStackTrace(pw); pw.println(); pw.flush(); selfAppender.append(sw.toString()); } catch (Throwable t) { } } } static public void flush() { EagleEyeLogDaemon.flushAndWait(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; public abstract class EagleEyeAppender { public abstract void append(String log); public void flush() { // do nothing } public void rollOver() { // do nothing } public void reload() { // do nothing } public void close() { // do nothing } public void cleanup() { // do nothing } public String getOutputLocation() { return null; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; final class EagleEyeCoreUtils { public static final String EMPTY_STRING = ""; public static final String NEWLINE = "\r\n"; public static final String[] EMPTY_STRING_ARRAY = new String[0]; 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)))) { return false; } } return true; } public static String checkNotNullEmpty(String value, String name) throws IllegalArgumentException { if (isBlank(value)) { throw new IllegalArgumentException(name + " is null or empty"); } return value; } public static T checkNotNull(T value, String name) throws IllegalArgumentException { if (value == null) { throw new IllegalArgumentException(name + " is null"); } return value; } public static T defaultIfNull(T value, T defaultValue) { return (value == null) ? defaultValue : value; } public static boolean isNotBlank(String str) { return !isBlank(str); } public static boolean isNotEmpty(String str) { return str != null && str.length() > 0; } public static String trim(String str) { return str == null ? null : str.trim(); } public static String[] split(String str, char separatorChar) { return splitWorker(str, separatorChar, false); } private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) { if (str == null) { return null; } int len = str.length(); if (len == 0) { return EMPTY_STRING_ARRAY; } List list = new ArrayList(); int i = 0, start = 0; boolean match = false; boolean lastMatch = false; while (i < len) { if (str.charAt(i) == separatorChar) { if (match || preserveAllTokens) { list.add(str.substring(start, i)); match = false; lastMatch = true; } start = ++i; continue; } lastMatch = false; match = true; i++; } if (match || (preserveAllTokens && lastMatch)) { list.add(str.substring(start, i)); } return list.toArray(new String[list.size()]); } public static StringBuilder appendWithBlankCheck(String str, String defaultValue, StringBuilder appender) { if (isNotBlank(str)) { appender.append(str); } else { appender.append(defaultValue); } return appender; } public static StringBuilder appendWithNullCheck(Object obj, String defaultValue, StringBuilder appender) { if (obj != null) { appender.append(obj.toString()); } else { appender.append(defaultValue); } return appender; } public static StringBuilder appendLog(String str, StringBuilder appender, char delimiter) { if (str != null) { int len = str.length(); appender.ensureCapacity(appender.length() + len); for (int i = 0; i < len; i++) { char c = str.charAt(i); if (c == '\n' || c == '\r' || c == delimiter) { c = ' '; } appender.append(c); } } return appender; } private static final DateTimeFormatter dateFmt = DateTimeFormatter .ofPattern("yyyy-MM-dd HH:mm:ss.SSS") .withZone(ZoneId.systemDefault()); public static String formatTime(long timestamp) { return dateFmt.format(Instant.ofEpochMilli(timestamp)); } public static String getSystemProperty(String key) { try { return System.getProperty(key); } catch (Throwable t) { return null; } } public static long getSystemPropertyForLong(String key, long defaultValue) { try { return Long.parseLong(System.getProperty(key)); } catch (Throwable t) { return defaultValue; } } public static boolean isHexNumeric(char ch) { return (ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9'); } public static boolean isNumeric(char ch) { return ch >= '0' && ch <= '9'; } public static void shutdownThreadPool(ExecutorService pool, long awaitTimeMillis) { try { pool.shutdown(); boolean done = false; if (awaitTimeMillis > 0) { try { done = pool.awaitTermination(awaitTimeMillis, TimeUnit.MILLISECONDS); } catch (Exception e) { } } if (!done) { pool.shutdownNow(); } } catch (Exception e) { // quietly } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; class EagleEyeLogDaemon implements Runnable { private static final long LOG_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(20); private static AtomicBoolean running = new AtomicBoolean(false); private static Thread worker = null; private static final CopyOnWriteArrayList watchedAppenders = new CopyOnWriteArrayList(); static EagleEyeAppender watch(EagleEyeAppender appender) { watchedAppenders.addIfAbsent(appender); return appender; } static boolean unwatch(EagleEyeAppender appender) { return watchedAppenders.remove(appender); } @Override public void run() { while (running.get()) { cleanupFiles(); try { Thread.sleep(LOG_CHECK_INTERVAL); } catch (InterruptedException e) { } flushAndReload(); } } private void cleanupFiles() { for (EagleEyeAppender watchedAppender : watchedAppenders) { try { watchedAppender.cleanup(); } catch (Exception e) { EagleEye.selfLog("[ERROR] fail to cleanup: " + watchedAppender, e); } } try { EagleEye.selfAppender.cleanup(); } catch (Exception e) { // quietly } } private void flushAndReload() { for (EagleEyeAppender watchedAppender : watchedAppenders) { try { watchedAppender.reload(); } catch (Exception e) { EagleEye.selfLog("[ERROR] fail to reload: " + watchedAppender, e); } } try { EagleEye.selfAppender.reload(); } catch (Exception e) { // quietly } } static void start() { if (running.compareAndSet(false, true)) { Thread worker = new Thread(new EagleEyeLogDaemon()); worker.setDaemon(true); worker.setName("EagleEye-LogDaemon-Thread"); worker.start(); EagleEyeLogDaemon.worker = worker; } } static void stop() { if (running.compareAndSet(true, false)) { closeAppenders(); final Thread worker = EagleEyeLogDaemon.worker; if (worker != null) { try { worker.interrupt(); } catch (Exception e) { // ignore } try { worker.join(1000); } catch (Exception e) { // ignore } } } } private static void closeAppenders() { for (EagleEyeAppender watchedAppender : watchedAppenders) { try { watchedAppender.close(); } catch (Exception e) { EagleEye.selfLog("[ERROR] fail to close: " + watchedAppender, e); } } } static void flushAndWait() { for (EagleEyeAppender watchedAppender : watchedAppenders) { try { watchedAppender.flush(); } catch (Exception e) { EagleEye.selfLog("[ERROR] fail to flush: " + watchedAppender, e); } } } private EagleEyeLogDaemon() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileLock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; class EagleEyeRollingFileAppender extends EagleEyeAppender { private static final long LOG_FLUSH_INTERVAL = TimeUnit.SECONDS.toMillis(1); private static final int DEFAULT_BUFFER_SIZE = 4 * 1024; // 4KB private final int maxBackupIndex = 3; private final long maxFileSize; private final int bufferSize = DEFAULT_BUFFER_SIZE; private final String filePath; private final AtomicBoolean isRolling = new AtomicBoolean(false); private BufferedOutputStream bos = null; private long nextFlushTime = 0L; private long lastRollOverTime = 0L; private long outputByteSize = 0L; private final boolean selfLogEnabled; private boolean multiProcessDetected = false; private static final String DELETE_FILE_SUFFIX = ".deleted"; public EagleEyeRollingFileAppender(String filePath, long maxFileSize) { this(filePath, maxFileSize, true); } public EagleEyeRollingFileAppender(String filePath, long maxFileSize, boolean selfLogEnabled) { this.filePath = filePath; this.maxFileSize = maxFileSize; this.selfLogEnabled = selfLogEnabled; setFile(); } private void setFile() { try { File logFile = new File(filePath); if (!logFile.exists()) { File parentFile = logFile.getParentFile(); if (!parentFile.exists() && !parentFile.mkdirs()) { doSelfLog("[ERROR] Fail to mkdirs: " + parentFile.getAbsolutePath()); return; } try { if (!logFile.createNewFile()) { doSelfLog("[ERROR] Fail to create file, it exists: " + logFile.getAbsolutePath()); } } catch (IOException e) { doSelfLog( "[ERROR] Fail to create file: " + logFile.getAbsolutePath() + ", error=" + e.getMessage()); } } if (!logFile.isFile() || !logFile.canWrite()) { doSelfLog("[ERROR] Invalid file, exists=" + logFile.exists() + ", isFile=" + logFile.isFile() + ", canWrite=" + logFile.canWrite() + ", path=" + logFile.getAbsolutePath()); return; } FileOutputStream ostream = new FileOutputStream(logFile, true); // true // O_APPEND this.bos = new BufferedOutputStream(ostream, bufferSize); this.lastRollOverTime = System.currentTimeMillis(); this.outputByteSize = logFile.length(); } catch (Throwable e) { doSelfLog("[ERROR] Fail to create file to write: " + filePath + ", error=" + e.getMessage()); } } @Override public void append(String log) { BufferedOutputStream bos = this.bos; if (bos != null) { try { waitUntilRollFinish(); byte[] bytes = log.getBytes(EagleEye.DEFAULT_CHARSET); int len = bytes.length; if (len > DEFAULT_BUFFER_SIZE && this.multiProcessDetected) { len = DEFAULT_BUFFER_SIZE; bytes[len - 1] = '\n'; } bos.write(bytes, 0, len); outputByteSize += len; if (outputByteSize >= maxFileSize) { rollOver(); } else { if (System.currentTimeMillis() >= nextFlushTime) { flush(); } } } catch (Exception e) { doSelfLog("[ERROR] fail to write log to file " + filePath + ", error=" + e.getMessage()); close(); setFile(); } } } @Override public void flush() { final BufferedOutputStream bos = this.bos; if (bos != null) { try { bos.flush(); nextFlushTime = System.currentTimeMillis() + LOG_FLUSH_INTERVAL; } catch (Exception e) { doSelfLog("[WARN] Fail to flush OutputStream: " + filePath + ", " + e.getMessage()); } } } @Override public void rollOver() { final String lockFilePath = filePath + ".lock"; final File lockFile = new File(lockFilePath); RandomAccessFile raf = null; FileLock fileLock = null; if (!isRolling.compareAndSet(false, true)) { return; } try { raf = new RandomAccessFile(lockFile, "rw"); fileLock = raf.getChannel().tryLock(); if (fileLock != null) { File target; File file; final int maxBackupIndex = this.maxBackupIndex; reload(); if (outputByteSize >= maxFileSize) { file = new File(filePath + '.' + maxBackupIndex); if (file.exists()) { target = new File(filePath + '.' + maxBackupIndex + DELETE_FILE_SUFFIX); if (!file.renameTo(target) && !file.delete()) { doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to " + target.getAbsolutePath()); } } for (int i = maxBackupIndex - 1; i >= 1; i--) { file = new File(filePath + '.' + i); if (file.exists()) { target = new File(filePath + '.' + (i + 1)); if (!file.renameTo(target) && !file.delete()) { doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to " + target.getAbsolutePath()); } } } target = new File(filePath + "." + 1); close(); file = new File(filePath); if (file.renameTo(target)) { doSelfLog("[INFO] File rolled to " + target.getAbsolutePath() + ", " + TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - lastRollOverTime) + " minutes since last roll"); } else { doSelfLog("[WARN] Fail to rename file: " + file.getAbsolutePath() + " to " + target.getAbsolutePath()); } setFile(); } } } catch (IOException e) { doSelfLog("[ERROR] Fail rollover file: " + filePath + ", error=" + e.getMessage()); } finally { isRolling.set(false); if (fileLock != null) { try { fileLock.release(); } catch (IOException e) { doSelfLog("[ERROR] Fail to release file lock: " + lockFilePath + ", error=" + e.getMessage()); } } if (raf != null) { try { raf.close(); } catch (IOException e) { doSelfLog("[WARN] Fail to close file lock: " + lockFilePath + ", error=" + e.getMessage()); } } if (fileLock != null) { if (!lockFile.delete() && lockFile.exists()) { doSelfLog("[WARN] Fail to delete file lock: " + lockFilePath); } } } } @Override public void close() { BufferedOutputStream bos = this.bos; if (bos != null) { try { bos.close(); } catch (IOException e) { doSelfLog("[WARN] Fail to close OutputStream: " + e.getMessage()); } this.bos = null; } } @Override public void reload() { flush(); File logFile = new File(filePath); long fileSize = logFile.length(); boolean fileNotExists = fileSize <= 0 && !logFile.exists(); if (this.bos == null || fileSize < outputByteSize || fileNotExists) { doSelfLog("[INFO] Log file rolled over by outside: " + filePath + ", force reload"); close(); setFile(); } else if (fileSize > outputByteSize) { this.outputByteSize = fileSize; if (!this.multiProcessDetected) { this.multiProcessDetected = true; if (selfLogEnabled) { doSelfLog("[WARN] Multi-process file write detected: " + filePath); } } } else { } } @Override public void cleanup() { try { File logFile = new File(filePath); File parentDir = logFile.getParentFile(); if (parentDir != null && parentDir.isDirectory()) { final String baseFileName = logFile.getName(); File[] filesToDelete = parentDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name != null && name.startsWith(baseFileName) && name.endsWith(DELETE_FILE_SUFFIX)) { return true; } return false; } }); if (filesToDelete != null && filesToDelete.length > 0) { for (File f : filesToDelete) { boolean success = f.delete() || !f.exists(); if (success) { doSelfLog("[INFO] Deleted log file: " + f.getAbsolutePath()); } else if (f.exists()) { doSelfLog("[ERROR] Fail to delete log file: " + f.getAbsolutePath()); } } } } } catch (Exception e) { doSelfLog("[ERROR] Fail to cleanup log file, error=" + e.getMessage()); } } void waitUntilRollFinish() { while (isRolling.get()) { try { Thread.sleep(1L); } catch (Exception e) { // quietly } } } private void doSelfLog(String log) { if (selfLogEnabled) { EagleEye.selfLog(log); } else { System.out.println("[EagleEye]" + log); } } @Override public String getOutputLocation() { return filePath; } @Override public String toString() { return "EagleEyeRollingFileAppender [filePath=" + filePath + "]"; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; class FastDateFormat { private final SimpleDateFormat fmt = createSimpleDateFormat(); private char[] buffer = new char[23]; private long lastSecond = -1; private long lastMillis = -1; public String format(long timestamp) { formatToBuffer(timestamp); return new String(buffer, 0, 23); } public String format(Date date) { return format(date.getTime()); } public void formatAndAppendTo(long timestamp, StringBuilder appender) { formatToBuffer(timestamp); appender.append(buffer, 0, 23); } private void formatToBuffer(long timestamp) { if (timestamp == lastMillis) { return; } long diff = timestamp - lastSecond; if (diff >= 0 && diff < 1000) { int ms = (int)(timestamp % 1000); buffer[22] = (char)(ms % 10 + '0'); ms /= 10; buffer[21] = (char)(ms % 10 + '0'); buffer[20] = (char)(ms / 10 + '0'); lastMillis = timestamp; } else { String result = fmt.format(new Date(timestamp)); result.getChars(0, result.length(), buffer, 0); lastSecond = timestamp / 1000 * 1000; lastMillis = timestamp; } } String formatWithoutMs(long timestamp) { long diff = timestamp - lastSecond; if (diff < 0 || diff >= 1000) { String result = fmt.format(new Date(timestamp)); result.getChars(0, result.length(), buffer, 0); lastSecond = timestamp / 1000 * 1000; lastMillis = timestamp; } return new String(buffer, 0, 19); } private SimpleDateFormat createSimpleDateFormat() { SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); fmt.setTimeZone(TimeZone.getDefault()); return fmt; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.Arrays; import java.util.List; public final class StatEntry { private final StatLogger statLogger; private final String[] keys; private transient int hash; public StatEntry(StatLogger statLogger, String key) { this.statLogger = statLogger; this.keys = new String[] {key}; } public StatEntry(StatLogger statLogger, String key1, String key2) { this.statLogger = statLogger; this.keys = new String[] {key1, key2}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3, key4}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3, key4, key5}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, String key6) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3, key4, key5, key6}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, String key6, String key7) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7}; } public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, String key6, String key7, String key8) { this.statLogger = statLogger; this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7, key8}; } public StatEntry(StatLogger statLogger, String key1, String... moreKeys) { String[] keys = new String[1 + moreKeys.length]; keys[0] = key1; for (int i = 0; i < moreKeys.length; ++i) { keys[i + 1] = moreKeys[i]; } this.statLogger = statLogger; this.keys = keys; } public StatEntry(StatLogger statLogger, List keys) { if (keys == null || keys.isEmpty()) { throw new IllegalArgumentException("keys empty or null: " + keys); } this.statLogger = statLogger; this.keys = keys.toArray(new String[keys.size()]); } public StatEntry(StatLogger statLogger, String[] keys) { if (keys == null || keys.length == 0) { throw new IllegalArgumentException("keys empty or null"); } this.statLogger = statLogger; this.keys = Arrays.copyOf(keys, keys.length); } public String[] getKeys() { return keys; } void appendTo(StringBuilder appender, char delimiter) { final int len = keys.length; if (len > 0) { appender.append(keys[0]); for (int i = 1; i < len; ++i) { appender.append(delimiter).append(keys[i]); } } } @Override public String toString() { StringBuilder sb = new StringBuilder(64); sb.append("StatKeys ["); appendTo(sb, ','); sb.append("]"); return sb.toString(); } @Override public int hashCode() { if (hash == 0) { int result = 1; result = 31 * result + Arrays.hashCode(keys); hash = result; } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } StatEntry other = (StatEntry)obj; if (hash != 0 && other.hash != 0 && hash != other.hash) { return false; } if (!Arrays.equals(keys, other.keys)) { return false; } return true; } StatEntryFunc getFunc(final StatEntryFuncFactory factory) { return this.statLogger.getRollingData().getStatEntryFunc(this, factory); } public void count() { count(1); } public void count(long count) { getFunc(StatEntryFuncFactory.COUNT_SUM).count(count); } public void countAndSum(long valueToSum) { countAndSum(1, valueToSum); } public void countAndSum(long count, long valueToSum) { getFunc(StatEntryFuncFactory.COUNT_SUM).countAndSum(count, valueToSum); } public void minMax(long candidate) { minMax(candidate, null); } public void minMax(long candidate, String ref) { getFunc(StatEntryFuncFactory.MIN_MAX).minMax(candidate, ref); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; interface StatEntryFunc { void appendTo(StringBuilder appender, char delimiter); int getStatType(); Object[] getValues(); void count(long count); void countAndSum(long count, long value); void arrayAdd(long... values); void arraySet(long... values); void minMax(long candidate, String ref); void batchAdd(long... values); void strArray(String... values); } enum StatEntryFuncFactory { COUNT_SUM { @Override StatEntryFunc create() { return new StatEntryFuncCountAndSum(); } }, MIN_MAX { @Override StatEntryFunc create() { return new StatEntryFuncMinMax(); } }; abstract StatEntryFunc create(); } class StatEntryFuncCountAndSum implements StatEntryFunc { private LongAdder count = new LongAdder(); private LongAdder value = new LongAdder(); @Override public void appendTo(StringBuilder appender, char delimiter) { appender.append(count.sum()).append(delimiter).append(value.sum()); } @Override public Object[] getValues() { return new Object[] {count.sum(), value.sum()}; } @Override public int getStatType() { return 1; } @Override public void count(long count) { this.count.add(count); } @Override public void countAndSum(long count, long value) { this.count.add(count); this.value.add(value); } @Override public void arrayAdd(long... values) { throw new IllegalStateException("arrayAdd() is unavailable if countAndSum() has been called"); } @Override public void arraySet(long... values) { throw new IllegalStateException("arraySet() is unavailable if countAndSum() has been called"); } @Override public void minMax(long candidate, String ref) { throw new IllegalStateException("minMax() is unavailable if countAndSum() has been called"); } @Override public void batchAdd(long... values) { throw new IllegalStateException("batchAdd() is unavailable if countAndSum() has been called"); } @Override public void strArray(String... values) { throw new IllegalStateException("strArray() is unavailable if countAndSum() has been called"); } } class StatEntryFuncMinMax implements StatEntryFunc { private AtomicReference max = new AtomicReference(new ValueRef(Long.MIN_VALUE, null)); private AtomicReference min = new AtomicReference(new ValueRef(Long.MAX_VALUE, null)); @Override public void appendTo(StringBuilder appender, char delimiter) { ValueRef lmax = max.get(); ValueRef lmin = min.get(); appender.append(lmax.value).append(delimiter); if (lmax.ref != null) { appender.append(lmax.ref); } appender.append(delimiter); appender.append(lmin.value).append(delimiter); if (lmin.ref != null) { appender.append(lmin.ref); } } @Override public Object[] getValues() { ValueRef lmax = max.get(); ValueRef lmin = min.get(); return new Object[] {lmax.value, lmax.ref, lmin.value, lmin.ref}; } @Override public int getStatType() { return 4; } @Override public void count(long count) { throw new IllegalStateException("count() is unavailable if minMax() has been called"); } @Override public void countAndSum(long count, long value) { throw new IllegalStateException("countAndSum() is unavailable if minMax() has been called"); } @Override public void arrayAdd(long... values) { throw new IllegalStateException("arrayAdd() is unavailable if minMax() has been called"); } @Override public void arraySet(long... values) { throw new IllegalStateException("arraySet() is unavailable if minMax() has been called"); } @Override public void batchAdd(long... values) { throw new IllegalStateException("batchAdd() is unavailable if minMax() has been called"); } @Override public void minMax(long candidate, String ref) { ValueRef lmax = max.get(); if (lmax.value <= candidate) { final ValueRef cmax = new ValueRef(candidate, ref); while (!max.compareAndSet(lmax, cmax) && (lmax = max.get()).value <= candidate) { ; } } ValueRef lmin = min.get(); if (lmin.value >= candidate) { final ValueRef cmin = new ValueRef(candidate, ref); while (!min.compareAndSet(lmin, cmin) && (lmin = min.get()).value >= candidate) { ; } } } @Override public void strArray(String... values) { throw new IllegalStateException("strArray() is unavailable if minMax() has been called"); } private static final class ValueRef { final long value; final String ref; ValueRef(long value, String ref) { this.value = value; this.ref = ref; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; class StatLogController { private static final Map statLoggers = new ConcurrentHashMap(); private static final int STAT_ENTRY_COOL_DOWN_MILLIS = 200; private static final ScheduledThreadPoolExecutor rollerThreadPool = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory( "EagleEye-StatLogController-roller", true)); private static final ScheduledThreadPoolExecutor writerThreadPool = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory( "EagleEye-StatLogController-writer", true)); private static AtomicBoolean running = new AtomicBoolean(false); static StatLogger createLoggerIfNotExists(StatLoggerBuilder builder) { String loggerName = builder.getLoggerName(); StatLogger statLogger = statLoggers.get(loggerName); if (statLogger == null) { synchronized (StatLogController.class) { if ((statLogger = statLoggers.get(loggerName)) == null) { statLogger = builder.create(); statLoggers.put(loggerName, statLogger); writerThreadPool.setMaximumPoolSize(Math.max(1, statLoggers.size())); scheduleNextRollingTask(statLogger); EagleEye.selfLog("[INFO] created statLogger[" + statLogger.getLoggerName() + "]: " + statLogger.getAppender()); } } } return statLogger; } static Map getAllStatLoggers() { return Collections.unmodifiableMap(statLoggers); } private static void scheduleNextRollingTask(StatLogger statLogger) { if (!running.get()) { EagleEye.selfLog("[INFO] stopped rolling statLogger[" + statLogger.getLoggerName() + "]"); return; } StatLogRollingTask rollingTask = new StatLogRollingTask(statLogger); long rollingTimeMillis = statLogger.getRollingData().getRollingTimeMillis(); long delayMillis = rollingTimeMillis - System.currentTimeMillis(); if (delayMillis > 5) { rollerThreadPool.schedule(rollingTask, delayMillis, TimeUnit.MILLISECONDS); } else if (-delayMillis > statLogger.getIntervalMillis()) { EagleEye.selfLog("[WARN] unusual delay of statLogger[" + statLogger.getLoggerName() + "], delay=" + (-delayMillis) + "ms, submit now"); rollerThreadPool.submit(rollingTask); } else { rollerThreadPool.submit(rollingTask); } } static void scheduleWriteTask(StatRollingData statRollingData) { if (statRollingData != null) { try { StatLogWriteTask task = new StatLogWriteTask(statRollingData); writerThreadPool.schedule(task, STAT_ENTRY_COOL_DOWN_MILLIS, TimeUnit.MILLISECONDS); } catch (Throwable t) { EagleEye.selfLog("[ERROR] fail to roll statLogger[" + statRollingData.getStatLogger().getLoggerName() + "]", t); } } } private static class StatLogRollingTask implements Runnable { final StatLogger statLogger; StatLogRollingTask(StatLogger statLogger) { this.statLogger = statLogger; } @Override public void run() { scheduleWriteTask(statLogger.rolling()); scheduleNextRollingTask(statLogger); } } private static class StatLogWriteTask implements Runnable { final StatRollingData statRollingData; StatLogWriteTask(StatRollingData statRollingData) { this.statRollingData = statRollingData; } @Override public void run() { final StatRollingData data = statRollingData; final StatLogger logger = data.getStatLogger(); try { final FastDateFormat fmt = new FastDateFormat(); final StringBuilder buffer = new StringBuilder(256); final String timeStr = fmt.formatWithoutMs(data.getTimeSlot()); final EagleEyeAppender appender = logger.getAppender(); final Set> entrySet = data.getStatEntrySet(); final char entryDelimiter = logger.getEntryDelimiter(); final char keyDelimiter = logger.getKeyDelimiter(); final char valueDelimiter = logger.getValueDelimiter(); for (Entry entry : entrySet) { buffer.delete(0, buffer.length()); StatEntryFunc func = entry.getValue(); // time|statType|keys|values buffer.append(timeStr).append(entryDelimiter); buffer.append(func.getStatType()).append(entryDelimiter); entry.getKey().appendTo(buffer, keyDelimiter); buffer.append(entryDelimiter); func.appendTo(buffer, valueDelimiter); buffer.append(EagleEyeCoreUtils.NEWLINE); appender.append(buffer.toString()); } appender.flush(); } catch (Throwable t) { EagleEye.selfLog("[WARN] fail to write statLogger[" + logger.getLoggerName() + "]", t); } } } static void start() { if (running.compareAndSet(false, true)) { rollerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); writerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(true); } } static void stop() { if (running.compareAndSet(true, false)) { EagleEyeCoreUtils.shutdownThreadPool(rollerThreadPool, 0); EagleEye.selfLog("[INFO] StatLoggerController: roller ThreadPool shutdown successfully"); for (StatLogger statLogger : statLoggers.values()) { new StatLogRollingTask(statLogger).run(); } try { Thread.sleep(STAT_ENTRY_COOL_DOWN_MILLIS); } catch (InterruptedException e) { // quietly } EagleEyeCoreUtils.shutdownThreadPool(writerThreadPool, 2000); EagleEye.selfLog("[INFO] StatLoggerController: writer ThreadPool shutdown successfully"); } } private StatLogController() { } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogger.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** * @author jifeng */ public final class StatLogger { private final String loggerName; private final EagleEyeAppender appender; private final AtomicReference ref; private final long intervalMillis; private final int maxEntryCount; private final char entryDelimiter; private final char keyDelimiter; private final char valueDelimiter; StatLogger(String loggerName, EagleEyeAppender appender, long intervalMillis, int maxEntryCount, char entryDelimiter, char keyDelimiter, char valueDelimiter) { this.loggerName = loggerName; this.appender = appender; this.intervalMillis = intervalMillis; this.maxEntryCount = maxEntryCount; this.entryDelimiter = entryDelimiter; this.keyDelimiter = keyDelimiter; this.valueDelimiter = valueDelimiter; this.ref = new AtomicReference(); rolling(); } public String getLoggerName() { return loggerName; } EagleEyeAppender getAppender() { return appender; } StatRollingData getRollingData() { return ref.get(); } long getIntervalMillis() { return intervalMillis; } int getMaxEntryCount() { return maxEntryCount; } char getEntryDelimiter() { return entryDelimiter; } char getKeyDelimiter() { return keyDelimiter; } char getValueDelimiter() { return valueDelimiter; } StatRollingData rolling() { do { long now = System.currentTimeMillis(); long timeSlot = now - now % intervalMillis; StatRollingData prevData = ref.get(); long rollingTimeMillis = timeSlot + intervalMillis; int initialCapacity = prevData != null ? prevData.getStatCount() : 16; StatRollingData nextData = new StatRollingData( this, initialCapacity, timeSlot, rollingTimeMillis); if (ref.compareAndSet(prevData, nextData)) { return prevData; } } while (true); } public StatEntry stat(String key) { return new StatEntry(this, key); } public StatEntry stat(String key1, String key2) { return new StatEntry(this, key1, key2); } public StatEntry stat(String key1, String key2, String key3) { return new StatEntry(this, key1, key2, key3); } public StatEntry stat(String key1, String key2, String key3, String key4) { return new StatEntry(this, key1, key2, key3, key4); } public StatEntry stat(String key1, String key2, String key3, String key4, String key5) { return new StatEntry(this, key1, key2, key3, key4, key5); } public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6) { return new StatEntry(this, key1, key2, key3, key4, key5, key6); } public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7) { return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7); } public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7, String key8) { return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7, key8); } public StatEntry stat(String key1, String... moreKeys) { return new StatEntry(this, key1, moreKeys); } public StatEntry stat(List keys) { return new StatEntry(this, keys); } public StatEntry stat(String[] keys) { return new StatEntry(this, keys); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.concurrent.TimeUnit; /** * @author jifeng */ public final class StatLoggerBuilder extends BaseLoggerBuilder { private int intervalSeconds = 60; private int maxEntryCount = 20000; private char keyDelimiter = ','; private char valueDelimiter = ','; private EagleEyeAppender appender = null; StatLoggerBuilder(String loggerName) { super(loggerName); } public StatLoggerBuilder intervalSeconds(int intervalSeconds) { validateInterval(intervalSeconds); this.intervalSeconds = intervalSeconds; return this; } public StatLoggerBuilder maxEntryCount(int maxEntryCount) { if (maxEntryCount < 1) { throw new IllegalArgumentException("Max entry count should be at least 1: " + maxEntryCount); } this.maxEntryCount = maxEntryCount; return this; } public StatLoggerBuilder keyDelimiter(char keyDelimiter) { this.keyDelimiter = keyDelimiter; return this; } public StatLoggerBuilder valueDelimiter(char valueDelimiter) { this.valueDelimiter = valueDelimiter; return this; } StatLoggerBuilder appender(EagleEyeAppender appender) { this.appender = appender; return this; } StatLogger create() { long intervalMillis = TimeUnit.SECONDS.toMillis(this.intervalSeconds); String filePath; if (this.filePath == null) { filePath = EagleEye.EAGLEEYE_LOG_DIR + "stat-" + loggerName + ".log"; } else if (this.filePath.endsWith("/") || this.filePath.endsWith("\\")) { filePath = this.filePath + "stat-" + loggerName + ".log"; } else { filePath = this.filePath; } EagleEyeAppender appender = this.appender; if (appender == null) { EagleEyeRollingFileAppender rfAppender = new EagleEyeRollingFileAppender(filePath, maxFileSize); appender = new SyncAppender(rfAppender); } EagleEyeLogDaemon.watch(appender); return new StatLogger(loggerName, appender, intervalMillis, maxEntryCount, entryDelimiter, keyDelimiter, valueDelimiter); } public StatLogger buildSingleton() { return StatLogController.createLoggerIfNotExists(this); } static void validateInterval(final long intervalSeconds) throws IllegalArgumentException { if (intervalSeconds < 1) { throw new IllegalArgumentException("Interval cannot be less than 1" + intervalSeconds); } else if (intervalSeconds < 60) { if (60 % intervalSeconds != 0) { throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); } } else if (intervalSeconds <= 5 * 60) { if (intervalSeconds % 60 != 0) { throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); } if (60 % intervalSeconds != 0) { throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); } } else if (intervalSeconds > 5 * 60) { throw new IllegalArgumentException("Interval should be less than 5 min: " + intervalSeconds); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * @author jifeng */ final class StatRollingData { private final StatLogger statLogger; private final long timeSlot; private final long rollingTimeMillis; private final ReentrantLock writeLock; private final Map statMap; StatRollingData(StatLogger statLogger, int initialCapacity, long timeSlot, long rollingTimeMillis) { this(statLogger, timeSlot, rollingTimeMillis, new ConcurrentHashMap( Math.min(initialCapacity, statLogger.getMaxEntryCount()))); } private StatRollingData(StatLogger statLogger, long timeSlot, long rollingTimeMillis, Map statMap) { this.statLogger = statLogger; this.timeSlot = timeSlot; this.rollingTimeMillis = rollingTimeMillis; this.writeLock = new ReentrantLock(); this.statMap = statMap; } StatEntryFunc getStatEntryFunc( final StatEntry statEntry, final StatEntryFuncFactory factory) { StatEntryFunc func = statMap.get(statEntry); if (func == null) { StatRollingData clone = null; writeLock.lock(); try { int entryCount = statMap.size(); if (entryCount < statLogger.getMaxEntryCount()) { func = statMap.get(statEntry); if (func == null) { func = factory.create(); statMap.put(statEntry, func); } } else { Map cloneStatMap = new HashMap(statMap); statMap.clear(); func = factory.create(); statMap.put(statEntry, func); clone = new StatRollingData(statLogger, timeSlot, rollingTimeMillis, cloneStatMap); } } finally { writeLock.unlock(); } if (clone != null) { StatLogController.scheduleWriteTask(clone); } } return func; } StatLogger getStatLogger() { return statLogger; } long getRollingTimeMillis() { return rollingTimeMillis; } long getTimeSlot() { return timeSlot; } int getStatCount() { return statMap.size(); } Set> getStatEntrySet() { return statMap.entrySet(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; /** * @author jifeng */ final class SyncAppender extends EagleEyeAppender { private final EagleEyeAppender delegate; private final Object lock = new Object(); public SyncAppender(EagleEyeAppender delegate) { this.delegate = delegate; } @Override public void append(String log) { synchronized (lock) { delegate.append(log); } } @Override public void flush() { synchronized (lock) { delegate.flush(); } } @Override public void rollOver() { synchronized (lock) { delegate.rollOver(); } } @Override public void reload() { synchronized (lock) { delegate.reload(); } } @Override public void close() { synchronized (lock) { delegate.close(); } } @Override public void cleanup() { delegate.cleanup(); } @Override public String getOutputLocation() { return delegate.getOutputLocation(); } @Override public String toString() { return "SyncAppender [appender=" + delegate + "]"; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.concurrent.atomic.AtomicLong; class TokenBucket { private final long maxTokens; private final long intervalMillis; private volatile long nextUpdate; private AtomicLong tokens; public TokenBucket(long maxTokens, long intervalMillis) { if (maxTokens <= 0) { throw new IllegalArgumentException("maxTokens should > 0, but given: " + maxTokens); } if (intervalMillis < 1000) { throw new IllegalArgumentException("intervalMillis should be at least 1000, but given: " + intervalMillis); } this.maxTokens = maxTokens; this.intervalMillis = intervalMillis; this.nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis; this.tokens = new AtomicLong(maxTokens); } public boolean accept(long now) { long currTokens; if (now > nextUpdate) { currTokens = tokens.get(); if (tokens.compareAndSet(currTokens, maxTokens)) { nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis; } } do { currTokens = tokens.get(); } while (currTokens > 0 && !tokens.compareAndSet(currTokens, currTokens - 1)); return currTokens > 0; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.init; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * Load registered init functions and execute in order. * * @author Eric Zhao */ public final class InitExecutor { private static AtomicBoolean initialized = new AtomicBoolean(false); /** * If one {@link InitFunc} throws an exception, the init process * will immediately be interrupted and the application will exit. * * The initialization will be executed only once. */ public static void doInit() { if (!initialized.compareAndSet(false, true)) { return; } try { List initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted(); List initList = new ArrayList(); for (InitFunc initFunc : initFuncs) { RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName()); insertSorted(initList, initFunc); } for (OrderWrapper w : initList) { w.func.init(); RecordLog.info("[InitExecutor] Executing {} with order {}", w.func.getClass().getCanonicalName(), w.order); } } catch (Exception ex) { RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex); ex.printStackTrace(); } catch (Error error) { RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error); error.printStackTrace(); } } private static void insertSorted(List list, InitFunc func) { int order = resolveOrder(func); int idx = 0; for (; idx < list.size(); idx++) { if (list.get(idx).getOrder() > order) { break; } } list.add(idx, new OrderWrapper(order, func)); } private static int resolveOrder(InitFunc func) { if (!func.getClass().isAnnotationPresent(InitOrder.class)) { return InitOrder.LOWEST_PRECEDENCE; } else { return func.getClass().getAnnotation(InitOrder.class).value(); } } private InitExecutor() {} private static class OrderWrapper { private final int order; private final InitFunc func; OrderWrapper(int order, InitFunc func) { this.order = order; this.func = func; } int getOrder() { return order; } InitFunc getFunc() { return func; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.init; /** * @author Eric Zhao */ public interface InitFunc { void init() throws Exception; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitOrder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.init; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author Eric Zhao */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface InitOrder { int LOWEST_PRECEDENCE = Integer.MAX_VALUE; int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; /** * The order value. Lowest precedence by default. * * @return the order value */ int value() default LOWEST_PRECEDENCE; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import java.io.File; import java.util.Properties; import java.util.logging.Level; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; /** *

The base config class for logging.

* *

* The default log base directory is {@code ${user.home}/logs/csp/}. We can use the {@link #LOG_DIR} * property to override it. The default log file name dose not contain pid, but if multi-instances of the same service * are running in the same machine, we may want to distinguish the log file by process ID number. * In this case, {@link #LOG_NAME_USE_PID} property could be configured as "true" to turn on this switch. *

* * @author Carpenter Lee * @author Eric Zhao */ public class LogBase { public static final String LOG_DIR = "csp.sentinel.log.dir"; public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid"; public static final String LOG_OUTPUT_TYPE = "csp.sentinel.log.output.type"; public static final String LOG_CHARSET = "csp.sentinel.log.charset"; public static final String LOG_LEVEL = "csp.sentinel.log.level"; /** * Output biz log (e.g. RecordLog and CommandCenterLog) to file. */ public static final String LOG_OUTPUT_TYPE_FILE = "file"; /** * Output biz log (e.g. RecordLog and CommandCenterLog) to console. */ public static final String LOG_OUTPUT_TYPE_CONSOLE = "console"; public static final String LOG_CHARSET_UTF8 = "utf-8"; private static final String DIR_NAME = "logs" + File.separator + "csp"; private static final String USER_HOME = "user.home"; private static final Level LOG_DEFAULT_LEVEL = Level.INFO; private static boolean logNameUsePid; private static String logOutputType; private static String logBaseDir; private static String logCharSet; private static Level logLevel; static { try { initializeDefault(); loadProperties(); } catch (Throwable t) { System.err.println("[LogBase] FATAL ERROR when initializing logging config"); t.printStackTrace(); } } private static void initializeDefault() { logNameUsePid = false; logOutputType = LOG_OUTPUT_TYPE_FILE; logBaseDir = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator; logCharSet = LOG_CHARSET_UTF8; logLevel = LOG_DEFAULT_LEVEL; } private static void loadProperties() { Properties properties = LogConfigLoader.getProperties(); logOutputType = properties.get(LOG_OUTPUT_TYPE) == null ? logOutputType : properties.getProperty(LOG_OUTPUT_TYPE); if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) { logOutputType = LOG_OUTPUT_TYPE_FILE; } System.out.println("INFO: Sentinel log output type is: " + logOutputType); logCharSet = properties.getProperty(LOG_CHARSET) == null ? logCharSet : properties.getProperty(LOG_CHARSET); System.out.println("INFO: Sentinel log charset is: " + logCharSet); logBaseDir = properties.getProperty(LOG_DIR) == null ? logBaseDir : properties.getProperty(LOG_DIR); logBaseDir = addSeparator(logBaseDir); File dir = new File(logBaseDir); if (!dir.exists()) { if (!dir.mkdirs()) { System.err.println("ERROR: create Sentinel log base directory error: " + logBaseDir); } } System.out.println("INFO: Sentinel log base directory is: " + logBaseDir); String usePid = properties.getProperty(LOG_NAME_USE_PID); logNameUsePid = "true".equalsIgnoreCase(usePid); System.out.println("INFO: Sentinel log name use pid is: " + logNameUsePid); // load log level String logLevelString = properties.getProperty(LOG_LEVEL); if (logLevelString != null && (logLevelString = logLevelString.trim()).length() > 0) { try { logLevel = Level.parse(logLevelString); } catch (IllegalArgumentException e) { System.out.println("Log level : " + logLevel + " is invalid. Use default : " + LOG_DEFAULT_LEVEL.toString()); } } System.out.println("INFO: Sentinel log level is: " + logLevel); } /** * Whether log file name should contain pid. This switch is configured by {@link #LOG_NAME_USE_PID} system property. * * @return true if log file name should contain pid, return true, otherwise false */ public static boolean isLogNameUsePid() { return logNameUsePid; } /** * Get the log file base directory path, which is guaranteed ended with {@link File#separator}. * * @return log file base directory path */ public static String getLogBaseDir() { return logBaseDir; } /** * Get the log file output type. * * @return log output type, "file" by default */ public static String getLogOutputType() { return logOutputType; } /** * Get the log file charset. * * @return the log file charset, "utf-8" by default */ public static String getLogCharset() { return logCharSet; } public static Level getLogLevel() { return logLevel; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogConfigLoader.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import com.alibaba.csp.sentinel.util.ConfigUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.Map; import java.util.Properties; import java.util.concurrent.CopyOnWriteArraySet; /** *

The loader that responsible for loading Sentinel log configurations.

* * @author lianglin * @since 1.7.0 */ public class LogConfigLoader { public static final String LOG_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE"; public static final String LOG_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file"; private static final String DEFAULT_LOG_CONFIG_FILE = "classpath:sentinel.properties"; private static final Properties properties = new Properties(); static { try { load(); } catch (Throwable t) { // NOTE: do not use RecordLog here, or there will be circular class dependency! System.err.println("[LogConfigLoader] Failed to initialize configuration items"); t.printStackTrace(); } } private static void load() { // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path String fileName = System.getProperty(LOG_CONFIG_PROPERTY_KEY); if (StringUtil.isBlank(fileName)) { fileName = System.getenv(LOG_CONFIG_ENV_KEY); if (StringUtil.isBlank(fileName)) { fileName = DEFAULT_LOG_CONFIG_FILE; } } Properties p = ConfigUtil.loadProperties(fileName); if (p != null && !p.isEmpty()) { properties.putAll(p); } CopyOnWriteArraySet> copy = new CopyOnWriteArraySet<>(System.getProperties().entrySet()); for (Map.Entry entry : copy) { String configKey = entry.getKey().toString(); String newConfigValue = entry.getValue().toString(); properties.put(configKey, newConfigValue); } } public static Properties getProperties() { return properties; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogTarget.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import java.lang.annotation.*; /** * @author xue8 * @since 1.7.2 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface LogTarget { /** * Returns the logger name. * * @return the logger name. Record logger by default */ String value() default RecordLog.LOGGER_NAME; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Logger.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; /** *

The universal logger SPI interface.

*

Notice: the placeholder only supports the most popular placeholder convention (slf4j). * So, if you're not using slf4j, you should create adapters compatible with placeholders "{}".

* * @author xue8 * @since 1.7.2 */ public interface Logger { /** * Log a message at the INFO level according to the specified format * and arguments. * * @param format the format string * @param arguments a list of arguments */ void info(String format, Object... arguments); /** * Log an exception (throwable) at the INFO level with an * accompanying message. * * @param msg the message accompanying the exception * @param e the exception (throwable) to log */ void info(String msg, Throwable e); /** * Log a message at the WARN level according to the specified format * and arguments. * * @param format the format string * @param arguments a list of arguments */ void warn(String format, Object... arguments); /** * Log an exception (throwable) at the WARN level with an * accompanying message. * * @param msg the message accompanying the exception * @param e the exception (throwable) to log */ void warn(String msg, Throwable e); /** * Log a message at the TRACE level according to the specified format * and arguments. * * @param format the format string * @param arguments a list of arguments */ void trace(String format, Object... arguments); /** * Log an exception (throwable) at the TRACE level with an * accompanying message. * * @param msg the message accompanying the exception * @param e the exception (throwable) to log */ void trace(String msg, Throwable e); /** * Log a message at the DEBUG level according to the specified format * and arguments. * * @param format the format string * @param arguments a list of arguments */ void debug(String format, Object... arguments); /** * Log an exception (throwable) at the DEBUG level with an * accompanying message. * * @param msg the message accompanying the exception * @param e the exception (throwable) to log */ void debug(String msg, Throwable e); /** * Log a message at the ERROR level according to the specified format * and arguments. * * @param format the format string * @param arguments a list of arguments */ void error(String format, Object... arguments); /** * Log an exception (throwable) at the ERROR level with an * accompanying message. * * @param msg the message accompanying the exception * @param e the exception (throwable) to log */ void error(String msg, Throwable e); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; import com.alibaba.csp.sentinel.util.StringUtil; /** * SPI provider of Sentinel {@link Logger}. * * @author Eric Zhao * @since 1.7.2 */ public final class LoggerSpiProvider { private static final Map LOGGER_MAP = new HashMap<>(); static { // NOTE: this class SHOULD NOT depend on any other Sentinel classes // except the util classes to avoid circular dependency. try { resolveLoggers(); } catch (Throwable t) { System.err.println("Failed to resolve Sentinel Logger SPI"); t.printStackTrace(); } } public static Logger getLogger(String name) { if (name == null) { return null; } return LOGGER_MAP.get(name); } private static void resolveLoggers() { // NOTE: Here we cannot use {@code SpiLoader} directly because it depends on the RecordLog. ServiceLoader loggerLoader = ServiceLoader.load(Logger.class); for (Logger logger : loggerLoader) { LogTarget annotation = logger.getClass().getAnnotation(LogTarget.class); if (annotation == null) { continue; } String name = annotation.value(); // Load first encountered logger if multiple loggers are associated with the same name. if (StringUtil.isNotBlank(name) && !LOGGER_MAP.containsKey(name)) { LOGGER_MAP.put(name, logger); System.out.println("Sentinel Logger SPI loaded for <" + name + ">: " + logger.getClass().getCanonicalName()); } } } private LoggerSpiProvider() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter; /** * The basic biz logger of Sentinel. * * @author youji.zj * @author Eric Zhao */ public class RecordLog { public static final String LOGGER_NAME = "sentinelRecordLogger"; public static final String DEFAULT_LOG_FILENAME = "sentinel-record.log"; private static com.alibaba.csp.sentinel.log.Logger logger = null; static { try { // Load user-defined logger implementation first. logger = LoggerSpiProvider.getLogger(LOGGER_NAME); if (logger == null) { // If no customized loggers are provided, we use the default logger based on JUL. logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME); } } catch (Throwable t) { System.err.println("Error: failed to initialize Sentinel RecordLog"); t.printStackTrace(); } } public static void info(String format, Object... arguments) { logger.info(format, arguments); } public static void info(String msg, Throwable e) { logger.info(msg, e); } public static void warn(String format, Object... arguments) { logger.warn(format, arguments); } public static void warn(String msg, Throwable e) { logger.warn(msg, e); } public static void trace(String format, Object... arguments) { logger.trace(format, arguments); } public static void trace(String msg, Throwable e) { logger.trace(msg, e); } public static void debug(String format, Object... arguments) { logger.debug(format, arguments); } public static void debug(String msg, Throwable e) { logger.debug(msg, e); } public static void error(String format, Object... arguments) { logger.error(format, arguments); } public static void error(String msg, Throwable e) { logger.error(msg, e); } private RecordLog() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import java.io.IOException; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.util.PidUtil; import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_CONSOLE; import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_FILE; /** * The default logger based on java.util.logging. * * @author Eric Zhao * @since 1.7.2 */ public class BaseJulLogger { protected void log(Logger logger, Handler handler, Level level, String detail, Object... params) { if (detail == null) { return; } disableOtherHandlers(logger, handler); // Compatible with slf4j placeholder format "{}". FormattingTuple formattingTuple = MessageFormatter.arrayFormat(detail, params); String message = formattingTuple.getMessage(); logger.log(level, message); } protected void log(Logger logger, Handler handler, Level level, String detail, Throwable throwable) { if (detail == null) { return; } disableOtherHandlers(logger, handler); logger.log(level, detail, throwable); } protected Handler makeLoggingHandler(String logName, Logger heliumRecordLog) { CspFormatter formatter = new CspFormatter(); String logCharSet = LogBase.getLogCharset(); Handler handler = null; // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET switch (LogBase.getLogOutputType()) { case LOG_OUTPUT_TYPE_FILE: String fileName = LogBase.getLogBaseDir() + logName; if (LogBase.isLogNameUsePid()) { fileName += ".pid" + PidUtil.getPid(); } try { handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); handler.setFormatter(formatter); handler.setEncoding(logCharSet); handler.setLevel(LogBase.getLogLevel()); } catch (IOException e) { e.printStackTrace(); } break; case LOG_OUTPUT_TYPE_CONSOLE: try { handler = new ConsoleHandler(); handler.setFormatter(formatter); handler.setEncoding(logCharSet); handler.setLevel(LogBase.getLogLevel()); } catch (IOException e) { e.printStackTrace(); } break; default: break; } if (handler != null) { disableOtherHandlers(heliumRecordLog, handler); } // Set log level to INFO by default heliumRecordLog.setLevel(LogBase.getLogLevel()); return handler; } /** * Remove all current handlers from the logger and attach it with the given log handler. * * @param logger logger * @param handler the log handler */ static void disableOtherHandlers(Logger logger, Handler handler) { if (logger == null) { return; } synchronized (logger) { Handler[] handlers = logger.getHandlers(); if (handlers == null) { return; } if (handlers.length == 1 && handlers[0].equals(handler)) { return; } logger.setUseParentHandlers(false); // Remove all current handlers. for (Handler h : handlers) { logger.removeHandler(h); } // Attach the given handler. logger.addHandler(handler); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import java.io.UnsupportedEncodingException; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.StreamHandler; /** * This Handler publishes log records to console by using {@link java.util.logging.StreamHandler}. * * Print log of WARNING level or above to System.err, * and print log of INFO level or below to System.out. * * To use this handler, add the following VM argument: *
 * -Dcsp.sentinel.log.output.type=console
 * 
* * @author cdfive */ class ConsoleHandler extends Handler { private static final ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 5, 1, TimeUnit.HOURS, new ArrayBlockingQueue<>(1024), new NamedThreadFactory("sentinel-console-log-executor", true), new LogRejectedExecutionHandler() ); static { executor.allowCoreThreadTimeOut(true); } /** * A Handler which publishes log records to System.out. */ private StreamHandler stdoutHandler; /** * A Handler which publishes log records to System.err. */ private StreamHandler stderrHandler; private AtomicReference> lastFuture = new AtomicReference<>(); public ConsoleHandler() { this.stdoutHandler = new StreamHandler(System.out, new CspFormatter()); this.stderrHandler = new StreamHandler(System.err, new CspFormatter()); } @Override public synchronized void setFormatter(Formatter newFormatter) throws SecurityException { this.stdoutHandler.setFormatter(newFormatter); this.stderrHandler.setFormatter(newFormatter); } @Override public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException { this.stdoutHandler.setEncoding(encoding); this.stderrHandler.setEncoding(encoding); } @Override public void publish(LogRecord record) { lastFuture.set(executor.submit(new LogTask(record, stdoutHandler, stderrHandler))); } @Override public void flush() { stdoutHandler.flush(); stderrHandler.flush(); } @Override public void close() throws SecurityException { Future future = lastFuture.get(); if (future != null) { try { future.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } stdoutHandler.close(); stderrHandler.close(); } static class LogRejectedExecutionHandler implements RejectedExecutionHandler { /** * The period of logged rejected records. */ private final long recordPeriod; private Long lastRecordTime; public LogRejectedExecutionHandler() { String DEFAULT_REJECTED_RECORD_PERIOD = "60000"; String REJECTED_RECORD_PERIOD_KEY = "sentinel.rejected.record.period"; lastRecordTime = null; recordPeriod = Long.parseLong(System.getProperty(REJECTED_RECORD_PERIOD_KEY, DEFAULT_REJECTED_RECORD_PERIOD)); } public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { long currentTimestamp = System.currentTimeMillis(); if (lastRecordTime == null || currentTimestamp - lastRecordTime > recordPeriod) { System.err.println("Failed to log sentinel record with console, rejected"); lastRecordTime = currentTimestamp; } } } static class LogTask implements Runnable { private final LogRecord record; private final StreamHandler stdoutHandler; private final StreamHandler stderrHandler; public LogTask(LogRecord record,StreamHandler stdoutHandler,StreamHandler stderrHandler) { this.record = record; this.stdoutHandler = stdoutHandler; this.stderrHandler = stderrHandler; } public void run() { if (record.getLevel().intValue() >= Level.WARNING.intValue()) { stderrHandler.publish(record); stderrHandler.flush(); } else { stdoutHandler.publish(record); stdoutHandler.flush(); } } public LogRecord getRecord() { return record; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/CspFormatter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.logging.Formatter; import java.util.logging.LogRecord; /** * @author xuyue */ class CspFormatter extends Formatter { private final DateTimeFormatter dateFormat = DateTimeFormatter .ofPattern("yyyy-MM-dd HH:mm:ss.SSS") .withZone(ZoneId.systemDefault()); @Override public String format(LogRecord record) { StringBuilder builder = new StringBuilder(1000); builder.append(dateFormat.format(Instant.ofEpochMilli(record.getMillis()))).append(" "); builder.append(record.getLevel().getName()).append(" "); builder.append(formatMessage(record)); String throwable = ""; if (record.getThrown() != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println(); record.getThrown().printStackTrace(pw); pw.close(); throwable = sw.toString(); } builder.append(throwable); if ("".equals(throwable)) { builder.append("\n"); } return builder.toString(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import java.io.File; import java.io.IOException; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.LogRecord; class DateFileLogHandler extends Handler { private final DateTimeFormatter dateFormat = DateTimeFormatter .ofPattern("yyyy-MM-dd") .withZone(ZoneId.systemDefault()); private static final ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 5, 1, TimeUnit.HOURS, new ArrayBlockingQueue(1024), new NamedThreadFactory("sentinel-datafile-log-executor", true), new ThreadPoolExecutor.DiscardOldestPolicy() ); static { // allow all thread could be stopped executor.allowCoreThreadTimeOut(true); } private volatile FileHandler handler; private final String pattern; private final int limit; private final int count; private final boolean append; private volatile boolean initialized = false; private volatile long startDate = System.currentTimeMillis(); private volatile long endDate; private final Object monitor = new Object(); DateFileLogHandler(String pattern, int limit, int count, boolean append) throws SecurityException { this.pattern = pattern; this.limit = limit; this.count = count; this.append = append; rotateDate(); this.initialized = true; } @Override public void close() throws SecurityException { handler.close(); } @Override public void flush() { handler.flush(); } @Override public void publish(LogRecord record) { if (shouldRotate(record)) { synchronized (monitor) { if (shouldRotate(record)) { rotateDate(); } } } if (System.currentTimeMillis() - startDate > 25 * 60 * 60 * 1000) { String msg = record.getMessage(); record.setMessage("missed file rolling at: " + new Date(endDate) + "\n" + msg); } executor.execute(new LogTask(record,handler)); } private boolean shouldRotate(LogRecord record) { if (endDate <= record.getMillis() || !logFileExits()) { return true; } return false; } @Override public void setFormatter(Formatter newFormatter) { super.setFormatter(newFormatter); if (handler != null) { handler.setFormatter(newFormatter); } } private boolean logFileExits() { try { String fileName = pattern.replace("%d", dateFormat.format(Instant.now())); // When file count is not 1, the first log file name will end with ".0" if (count != 1) { fileName += ".0"; } File logFile = new File(fileName); return logFile.exists(); } catch (Throwable e) { } return false; } private void rotateDate() { this.startDate = System.currentTimeMillis(); if (handler != null) { handler.close(); } String newPattern = pattern.replace("%d", dateFormat.format(Instant.now())); // Get current date. Calendar next = Calendar.getInstance(); // Begin of next date. next.set(Calendar.HOUR_OF_DAY, 0); next.set(Calendar.MINUTE, 0); next.set(Calendar.SECOND, 0); next.set(Calendar.MILLISECOND, 0); next.add(Calendar.DATE, 1); this.endDate = next.getTimeInMillis(); try { this.handler = new FileHandler(newPattern, limit, count, append); if (initialized) { handler.setEncoding(this.getEncoding()); handler.setErrorManager(this.getErrorManager()); handler.setFilter(this.getFilter()); handler.setFormatter(this.getFormatter()); handler.setLevel(this.getLevel()); } } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } static class LogRejectedExecutionHandler implements RejectedExecutionHandler { /** * The period of logged rejected records. */ private final long recordPeriod; private Long lastRecordTime; public LogRejectedExecutionHandler() { String DEFAULT_REJECTED_RECORD_PERIOD = "60000"; String REJECTED_RECORD_PERIOD_KEY = "sentinel.rejected.record.period"; lastRecordTime = null; recordPeriod = Long.parseLong(System.getProperty(REJECTED_RECORD_PERIOD_KEY, DEFAULT_REJECTED_RECORD_PERIOD)); } public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { long currentTimestamp = System.currentTimeMillis(); if (lastRecordTime == null || currentTimestamp - lastRecordTime > recordPeriod) { System.err.println("Failed to log sentinel record with datafile, rejected"); lastRecordTime = currentTimestamp; } } } static class LogTask implements Runnable { private final LogRecord record; private final FileHandler handler; public LogTask(LogRecord record,FileHandler handler) { this.record = record; this.handler = handler; } public void run() { handler.publish(record); } public LogRecord getRecord() { return record; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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 notice: This code was copied from SLF4J which licensed under the MIT License. package com.alibaba.csp.sentinel.log.jul; /** * 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; this.argArray = argArray; } public String getMessage() { return message; } public Object[] getArgArray() { return argArray; } public Throwable getThrowable() { return throwable; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import java.util.logging.Handler; import com.alibaba.csp.sentinel.log.Logger; import com.alibaba.csp.sentinel.util.AssertUtil; /** * JUL adapter for Sentinel {@link Logger} SPI. * * @author Eric Zhao * @since 1.7.2 */ public class JavaLoggingAdapter extends BaseJulLogger implements Logger { private final String loggerName; private final String fileNamePattern; private final java.util.logging.Logger julLogger; private final Handler logHandler; public JavaLoggingAdapter(String loggerName, String fileNamePattern) { AssertUtil.assertNotBlank(loggerName, "loggerName cannot be blank"); AssertUtil.assertNotBlank(fileNamePattern, "fileNamePattern cannot be blank"); this.loggerName = loggerName; this.fileNamePattern = fileNamePattern; this.julLogger = java.util.logging.Logger.getLogger(loggerName); this.logHandler = makeLoggingHandler(fileNamePattern, julLogger); } @Override public void info(String format, Object... arguments) { log(julLogger, logHandler, Level.INFO, format, arguments); } @Override public void info(String msg, Throwable e) { log(julLogger, logHandler, Level.INFO, msg, e); } @Override public void warn(String format, Object... arguments) { log(julLogger, logHandler, Level.WARNING, format, arguments); } @Override public void warn(String msg, Throwable e) { log(julLogger, logHandler, Level.WARNING, msg, e); } @Override public void trace(String format, Object... arguments) { log(julLogger, logHandler, Level.TRACE, format, arguments); } @Override public void trace(String msg, Throwable e) { log(julLogger, logHandler, Level.TRACE, msg, e); } @Override public void debug(String format, Object... arguments) { log(julLogger, logHandler, Level.DEBUG, format, arguments); } @Override public void debug(String msg, Throwable e) { log(julLogger, logHandler, Level.DEBUG, msg, e); } @Override public void error(String format, Object... arguments) { log(julLogger, logHandler, Level.ERROR, format, arguments); } @Override public void error(String msg, Throwable e) { log(julLogger, logHandler, Level.ERROR, msg, e); } public String getLoggerName() { return loggerName; } public String getFileNamePattern() { return fileNamePattern; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/Level.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; /** * JUL logging levels. * * @author xue8 */ public class Level extends java.util.logging.Level { private static final String defaultBundle = "sun.util.logging.resources.logging"; public static final Level ERROR = new Level("ERROR", 1000); public static final Level WARNING = new Level("WARNING", 900); public static final Level INFO = new Level("INFO", 800); public static final Level DEBUG = new Level("DEBUG", 700); public static final Level TRACE = new Level("TRACE", 600); protected Level(String name, int value) { super(name, value, defaultBundle); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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 notice: This code was copied from SLF4J which licensed under the MIT License. package com.alibaba.csp.sentinel.log.jul; // contributors: lizongbo: proposed special treatment of array parameter values // Joern Huxhorn: pointed out double[] omission, suggested deep array copy import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; /** * 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 = '\\'; /** * 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 arg * 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; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { Throwable throwableCandidate = getThrowableCandidate(argArray); Object[] args = argArray; if (throwableCandidate != null) { args = trimmedCopy(argArray); } return arrayFormat(messagePattern, args, throwableCandidate); } private 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; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; // use string builder for better multicore performance StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int L; for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented sbuf.append(messagePattern, 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, i, j - 1); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } else { // normal case sbuf.append(messagePattern, i, j); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } } // append the characters following the last {} pair. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } 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 >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { return true; } else { return false; } } // special treatment of array values was suggested by 'lizongbo' private static void deeplyAppendParameter(StringBuilder 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(StringBuilder sbuf, Object o) { try { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { sbuf.append("[FAILED toString()]"); } } private static void objectArrayAppend(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(StringBuilder 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(']'); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Extended {@link MetricExtension} extending input parameters of each metric * collection method with {@link EntryType}. * * @author bill_yip * @author Eric Zhao * @since 1.8.0 */ public interface AdvancedMetricExtension extends MetricExtension { /** * Add current pass count of the resource name. * * @param rw resource representation (including resource name, traffic type, etc.) * @param batchCount count to add * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void onPass(ResourceWrapper rw, int batchCount, Object[] args); /** * Add current block count of the resource name. * * @param rw resource representation (including resource name, traffic type, etc.) * @param batchCount count to add * @param origin the origin of caller (if present) * @param e the associated {@code BlockException} * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e, Object[] args); /** * Add current completed count of the resource name. * * @param rw resource representation (including resource name, traffic type, etc.) * @param batchCount count to add * @param rt response time of current invocation * @param args additional arguments of the resource */ void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args); /** * Add current exception count of the resource name. * * @param rw resource representation (including resource name, traffic type, etc.) * @param batchCount count to add * @param throwable exception related. * @param args additional arguments of the resource */ void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.metric.extension.callback.MetricEntryCallback; import com.alibaba.csp.sentinel.metric.extension.callback.MetricExitCallback; import com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry; /** * Register callbacks for metric extension. * * @author Carpenter Lee * @since 1.6.1 */ public class MetricCallbackInit implements InitFunc { @Override public void init() throws Exception { StatisticSlotCallbackRegistry.addEntryCallback(MetricEntryCallback.class.getCanonicalName(), new MetricEntryCallback()); StatisticSlotCallbackRegistry.addExitCallback(MetricExitCallback.class.getCanonicalName(), new MetricExitCallback()); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * This interface provides extension to Sentinel internal statistics. *

* Please note that all method in this class will invoke in the same thread of biz logic. * It's necessary to not do time-consuming operation in any of the interface's method, * otherwise biz logic will be blocked. *

* * @author Carpenter Lee * @since 1.6.1 */ public interface MetricExtension { /** * Add current pass count of the resource name. * * @param n count to add * @param resource resource name * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void addPass(String resource, int n, Object... args); /** * Add current block count of the resource name. * * @param n count to add * @param resource resource name * @param origin the original invoker. * @param blockException block exception related. * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void addBlock(String resource, int n, String origin, BlockException blockException, Object... args); /** * Add current completed count of the resource name. * * @param n count to add * @param resource resource name * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void addSuccess(String resource, int n, Object... args); /** * Add current exception count of the resource name. * * @param n count to add * @param resource resource name * @param throwable exception related. */ void addException(String resource, int n, Throwable throwable); /** * Add response time of the resource name. * * @param rt response time in millisecond * @param resource resource name * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void addRt(String resource, long rt, Object... args); /** * Increase current thread count of the resource name. * * @param resource resource name * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void increaseThreadNum(String resource, Object... args); /** * Decrease current thread count of the resource name. * * @param resource resource name * @param args additional arguments of the resource, eg. if the resource is a method name, * the args will be the parameters of the method. */ void decreaseThreadNum(String resource, Object... args); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * Get all {@link MetricExtension} via SPI. * * @author Carpenter Lee * @since 1.6.1 */ public class MetricExtensionProvider { private static List metricExtensions = new ArrayList<>(); static { resolveInstance(); } private static void resolveInstance() { List extensions = SpiLoader.of(MetricExtension.class).loadInstanceList(); if (extensions.isEmpty()) { RecordLog.info("[MetricExtensionProvider] No existing MetricExtension found"); } else { metricExtensions.addAll(extensions); RecordLog.info("[MetricExtensionProvider] MetricExtension resolved, size={}", extensions.size()); } } /** *

Get all registered metric extensions.

*

DO NOT MODIFY the returned list, use {@link #addMetricExtension(MetricExtension)}.

* * @return all registered metric extensions */ public static List getMetricExtensions() { return metricExtensions; } /** * Add metric extension. *

* Note that this method is NOT thread safe. *

* * @param metricExtension the metric extension to add. */ public static void addMetricExtension(MetricExtension metricExtension) { metricExtensions.add(metricExtension); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Metric extension entry callback. * * @author Carpenter Lee * @since 1.6.1 */ public class MetricEntryCallback implements ProcessorSlotEntryCallback { @Override public void onPass(Context context, ResourceWrapper rw, DefaultNode param, int count, Object... args) throws Exception { for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { if (m instanceof AdvancedMetricExtension) { ((AdvancedMetricExtension) m).onPass(rw, count, args); } else { m.increaseThreadNum(rw.getName(), args); m.addPass(rw.getName(), count, args); } } } @Override public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, Object... args) { for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { if (m instanceof AdvancedMetricExtension) { ((AdvancedMetricExtension) m).onBlocked(resourceWrapper, count, context.getOrigin(), ex, args); } else { m.addBlock(resourceWrapper.getName(), count, context.getOrigin(), ex, args); } } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Metric extension exit callback. * * @author Carpenter Lee * @author Eric Zhao * @since 1.6.1 */ public class MetricExitCallback implements ProcessorSlotExitCallback { @Override public void onExit(Context context, ResourceWrapper rw, int acquireCount, Object... args) { Entry curEntry = context.getCurEntry(); if (curEntry == null) { return; } for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { if (curEntry.getBlockError() != null) { continue; } String resource = rw.getName(); Throwable ex = curEntry.getError(); long completeTime = curEntry.getCompleteTimestamp(); if (completeTime <= 0) { completeTime = TimeUtil.currentTimeMillis(); } long rt = completeTime - curEntry.getCreateTimestamp(); if (m instanceof AdvancedMetricExtension) { // Since 1.8.0 (as a temporary workaround for compatibility) ((AdvancedMetricExtension) m).onComplete(rw, rt, acquireCount, args); if (ex != null) { ((AdvancedMetricExtension) m).onError(rw, ex, acquireCount, args); } } else { m.addRt(resource, rt, args); m.addSuccess(resource, acquireCount, args); m.decreaseThreadNum(resource, args); if (null != ex) { m.addException(resource, acquireCount, ex); } } } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.AssertUtil; /** *

* This class stores summary runtime statistics of the resource, including rt, thread count, qps * and so on. Same resource shares the same {@link ClusterNode} globally, no matter in which * {@link com.alibaba.csp.sentinel.context.Context}. *

*

* To distinguish invocation from different origin (declared in * {@link ContextUtil#enter(String name, String origin)}), * one {@link ClusterNode} holds an {@link #originCountMap}, this map holds {@link StatisticNode} * of different origin. Use {@link #getOrCreateOriginNode(String)} to get {@link Node} of the specific * origin.
* Note that 'origin' usually is Service Consumer's app name. *

* * @author qinan.qn * @author jialiang.linjl */ public class ClusterNode extends StatisticNode { private final String name; private final int resourceType; public ClusterNode(String name) { this(name, ResourceTypeConstants.COMMON); } public ClusterNode(String name, int resourceType) { AssertUtil.notEmpty(name, "name cannot be empty"); this.name = name; this.resourceType = resourceType; } /** *

The origin map holds the pair: (origin, originNode) for one specific resource.

*

* The longer the application runs, the more stable this mapping will become. * So we didn't use concurrent map here, but a lock, as this lock only happens * at the very beginning while concurrent map will hold the lock all the time. *

*/ private Map originCountMap = new HashMap<>(); private final ReentrantLock lock = new ReentrantLock(); /** * Get resource name of the resource node. * * @return resource name * @since 1.7.0 */ public String getName() { return name; } /** * Get classification (type) of the resource. * * @return resource type * @since 1.7.0 */ public int getResourceType() { return resourceType; } /** *

Get {@link Node} of the specific origin. Usually the origin is the Service Consumer's app name.

*

If the origin node for given origin is absent, then a new {@link StatisticNode} * for the origin will be created and returned.

* * @param origin The caller's name, which is designated in the {@code parameter} parameter * {@link ContextUtil#enter(String name, String origin)}. * @return the {@link Node} of the specific origin */ public Node getOrCreateOriginNode(String origin) { StatisticNode statisticNode = originCountMap.get(origin); if (statisticNode == null) { lock.lock(); try { statisticNode = originCountMap.get(origin); if (statisticNode == null) { // The node is absent, create a new node for the origin. statisticNode = new StatisticNode(); HashMap newMap = new HashMap<>(originCountMap.size() + 1); newMap.putAll(originCountMap); newMap.put(origin, statisticNode); originCountMap = newMap; } } finally { lock.unlock(); } } return statisticNode; } public Map getOriginCountMap() { return originCountMap; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.SphO; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; /** *

* A {@link Node} used to hold statistics for specific resource name in the specific context. * Each distinct resource in each distinct {@link Context} will corresponding to a {@link DefaultNode}. *

*

* This class may have a list of sub {@link DefaultNode}s. Child nodes will be created when * calling {@link SphU}#entry() or {@link SphO}@entry() multiple times in the same {@link Context}. *

* * @author qinan.qn * @see NodeSelectorSlot */ public class DefaultNode extends StatisticNode { /** * The resource associated with the node. */ private ResourceWrapper id; /** * The list of all child nodes. */ private volatile Set childList = new HashSet<>(); /** * Associated cluster node. */ private ClusterNode clusterNode; public DefaultNode(ResourceWrapper id, ClusterNode clusterNode) { this.id = id; this.clusterNode = clusterNode; } public ResourceWrapper getId() { return id; } public ClusterNode getClusterNode() { return clusterNode; } public void setClusterNode(ClusterNode clusterNode) { this.clusterNode = clusterNode; } /** * Add child node to current node. * * @param node valid child node */ public void addChild(Node node) { if (node == null) { RecordLog.warn("Trying to add null child to node <{}>, ignored", id.getName()); return; } if (!childList.contains(node)) { synchronized (this) { if (!childList.contains(node)) { Set newSet = new HashSet<>(childList.size() + 1); newSet.addAll(childList); newSet.add(node); childList = newSet; } } RecordLog.info("Add child <{}> to node <{}>", ((DefaultNode)node).id.getName(), id.getName()); } } /** * Reset the child node list. */ public void removeChildList() { this.childList = new HashSet<>(); } public Set getChildList() { return childList; } @Override public void increaseBlockQps(int count) { super.increaseBlockQps(count); this.clusterNode.increaseBlockQps(count); } @Override public void increaseExceptionQps(int count) { super.increaseExceptionQps(count); this.clusterNode.increaseExceptionQps(count); } @Override public void addRtAndSuccess(long rt, int successCount) { super.addRtAndSuccess(rt, successCount); this.clusterNode.addRtAndSuccess(rt, successCount); } @Override public void increaseThreadNum() { super.increaseThreadNum(); this.clusterNode.increaseThreadNum(); } @Override public void decreaseThreadNum() { super.decreaseThreadNum(); this.clusterNode.decreaseThreadNum(); } @Override public void addPassRequest(int count) { super.addPassRequest(count); this.clusterNode.addPassRequest(count); } public void printDefaultNode() { visitTree(0, this); } private void visitTree(int level, DefaultNode node) { for (int i = 0; i < level; ++i) { System.out.print("-"); } if (!(node instanceof EntranceNode)) { System.out.println( String.format("%s(thread:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(), node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())); } else { System.out.println( String.format("Entry-%s(t:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(), node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())); } for (Node n : node.getChildList()) { DefaultNode dn = (DefaultNode)n; visitTree(level + 1, dn); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/EntranceNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; /** *

* A {@link Node} represents the entrance of the invocation tree. *

*

* One {@link Context} will related to a {@link EntranceNode}, * which represents the entrance of the invocation tree. New {@link EntranceNode} will be created if * current context does't have one. Note that same context name will share same {@link EntranceNode} * globally. *

* * @author qinan.qn * @see ContextUtil * @see ContextUtil#enter(String, String) * @see NodeSelectorSlot */ public class EntranceNode extends DefaultNode { public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) { super(id, clusterNode); } @Override public double avgRt() { double total = 0; double totalQps = 0; for (Node node : getChildList()) { total += node.avgRt() * node.passQps(); totalQps += node.passQps(); } return total / (totalQps == 0 ? 1 : totalQps); } @Override public double blockQps() { double blockQps = 0; for (Node node : getChildList()) { blockQps += node.blockQps(); } return blockQps; } @Override public long blockRequest() { long r = 0; for (Node node : getChildList()) { r += node.blockRequest(); } return r; } @Override public int curThreadNum() { int r = 0; for (Node node : getChildList()) { r += node.curThreadNum(); } return r; } @Override public double totalQps() { double r = 0; for (Node node : getChildList()) { r += node.totalQps(); } return r; } @Override public double successQps() { double r = 0; for (Node node : getChildList()) { r += node.successQps(); } return r; } @Override public double passQps() { double r = 0; for (Node node : getChildList()) { r += node.passQps(); } return r; } @Override public long totalRequest() { long r = 0; for (Node node : getChildList()) { r += node.totalRequest(); } return r; } @Override public long totalPass() { long r = 0; for (Node node : getChildList()) { r += node.totalPass(); } return r; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** * QPS statistics interval. * * @author youji.zj * @author jialiang.linjl * @author Carpenter Lee * @author Eric Zhao */ public class IntervalProperty { /** *

Interval in milliseconds. This variable determines sensitivity of the QPS calculation.

*

* DO NOT MODIFY this value directly, use {@link #updateInterval(int)}, otherwise the modification will not * take effect. *

*/ public static volatile int INTERVAL = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; public static void register2Property(SentinelProperty property) { property.addListener(new SimplePropertyListener() { @Override public void configUpdate(Integer value) { if (value != null) { updateInterval(value); } } }); } /** * Update the {@link #INTERVAL}, All {@link ClusterNode}s will be reset if newInterval is * different from {@link #INTERVAL} * * @param newInterval New interval to set. */ public static void updateInterval(int newInterval) { if (newInterval != INTERVAL) { INTERVAL = newInterval; ClusterBuilderSlot.resetClusterNodes(); } RecordLog.info("[IntervalProperty] INTERVAL updated to: {}", INTERVAL); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import java.util.List; import java.util.Map; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport; import com.alibaba.csp.sentinel.util.function.Predicate; /** * Holds real-time statistics for resources. * * @author qinan.qn * @author leyou * @author Eric Zhao */ public interface Node extends OccupySupport, DebugSupport { /** * Get incoming request per minute ({@code pass + block}). * * @return total request count per minute */ long totalRequest(); /** * Get pass count per minute. * * @return total passed request count per minute * @since 1.5.0 */ long totalPass(); /** * Get {@link Entry#exit()} count per minute. * * @return total completed request count per minute */ long totalSuccess(); /** * Get blocked request count per minute (totalBlockRequest). * * @return total blocked request count per minute */ long blockRequest(); /** * Get exception count per minute. * * @return total business exception count per minute */ long totalException(); /** * Get pass request per second. * * @return QPS of passed requests */ double passQps(); /** * Get block request per second. * * @return QPS of blocked requests */ double blockQps(); /** * Get {@link #passQps()} + {@link #blockQps()} request per second. * * @return QPS of passed and blocked requests */ double totalQps(); /** * Get {@link Entry#exit()} request per second. * * @return QPS of completed requests */ double successQps(); /** * Get estimated max success QPS till now. * * @return max completed QPS */ double maxSuccessQps(); /** * Get exception count per second. * * @return QPS of exception occurs */ double exceptionQps(); /** * Get average rt per second. * * @return average response time per second */ double avgRt(); /** * Get minimal response time. * * @return recorded minimal response time */ double minRt(); /** * Get current active thread count. * * @return current active thread count */ int curThreadNum(); /** * Get last second block QPS. */ double previousBlockQps(); /** * Last window QPS. */ double previousPassQps(); /** * Fetch all valid metric nodes of resources. * * @return valid metric nodes of resources */ Map metrics(); /** * Fetch all raw metric items that satisfies the time predicate. * * @param timePredicate time predicate * @return raw metric items that satisfies the time predicate * @since 1.7.0 */ List rawMetricsInMin(Predicate timePredicate); /** * Add pass count. * * @param count count to add pass */ void addPassRequest(int count); /** * Add rt and success count. * * @param rt response time * @param success success count to add */ void addRtAndSuccess(long rt, int success); /** * Increase the block count. * * @param count count to add */ void increaseBlockQps(int count); /** * Add the biz exception count. * * @param count count to add */ void increaseExceptionQps(int count); /** * Increase current thread count. */ void increaseThreadNum(); /** * Decrease current thread count. */ void decreaseThreadNum(); /** * Reset the internal counter. Reset is needed when {@link IntervalProperty#INTERVAL} or * {@link SampleCountProperty#SAMPLE_COUNT} is changed. */ void reset(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/NodeBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; /** * Builds new {@link DefaultNode} and {@link ClusterNode}. * * @author qinan.qn */ @Deprecated public interface NodeBuilder { /** * Create a new {@link DefaultNode} as tree node. * * @param id resource * @param clusterNode the cluster node of the provided resource * @return new created tree node */ DefaultNode buildTreeNode(ResourceWrapper id, ClusterNode clusterNode); /** * Create a new {@link ClusterNode} as universal statistic node for a single resource. * * @return new created cluster node */ ClusterNode buildClusterNode(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupySupport.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; /** * @author Eric Zhao * @since 1.5.0 */ public interface OccupySupport { /** * Try to occupy latter time windows' tokens. If occupy success, a value less than * {@code occupyTimeout} in {@link OccupyTimeoutProperty} will be return. * *

* Each time we occupy tokens of the future window, current thread should sleep for the * corresponding time for smoothing QPS. We can't occupy tokens of the future with unlimited, * the sleep time limit is {@code occupyTimeout} in {@link OccupyTimeoutProperty}. *

* * @param currentTime current time millis. * @param acquireCount tokens count to acquire. * @param threshold qps threshold. * @return time should sleep. Time >= {@code occupyTimeout} in {@link OccupyTimeoutProperty} means * occupy fail, in this case, the request should be rejected immediately. */ long tryOccupyNext(long currentTime, int acquireCount, double threshold); /** * Get current waiting amount. Useful for debug. * * @return current waiting amount */ long waiting(); /** * Add request that occupied. * * @param futureTime future timestamp that the acquireCount should be added on. * @param acquireCount tokens count. */ void addWaitingRequest(long futureTime, int acquireCount); /** * Add occupied pass request, which represents pass requests that borrow the latter windows' token. * * @param acquireCount tokens count. */ void addOccupiedPass(int acquireCount); /** * Get current occupied pass QPS. * * @return current occupied pass QPS */ double occupiedPassQps(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.property.SimplePropertyListener; /** * @author jialiang.linjl * @author Carpenter Lee * @since 1.5.0 */ public class OccupyTimeoutProperty { /** *

* Max occupy timeout in milliseconds. Requests with priority can occupy tokens of the future statistic * window, and {@code occupyTimeout} limit the max time length that can be occupied. *

*

* Note that the timeout value should never be greeter than {@link IntervalProperty#INTERVAL}. *

* DO NOT MODIFY this value directly, use {@link #updateTimeout(int)}, * otherwise the modification will not take effect. */ private static volatile int occupyTimeout = 500; public static void register2Property(SentinelProperty property) { property.addListener(new SimplePropertyListener() { @Override public void configUpdate(Integer value) { if (value != null) { updateTimeout(value); } } }); } public static int getOccupyTimeout() { return occupyTimeout; } /** * Update the timeout value.
* Note that the time out should never greeter than {@link IntervalProperty#INTERVAL}, * or it will be ignored. * * @param newInterval new value. */ public static void updateTimeout(int newInterval) { if (newInterval < 0) { RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout); return; } if (newInterval > IntervalProperty.INTERVAL) { RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: {}, should <= {}", occupyTimeout, IntervalProperty.INTERVAL); return; } if (newInterval != occupyTimeout) { occupyTimeout = newInterval; } RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: {}", occupyTimeout); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** * Holds statistic buckets count per second. * * @author jialiang.linjl * @author CarpenterLee */ public class SampleCountProperty { /** *

* Statistic buckets count per second. This variable determines sensitivity of the QPS calculation. * DO NOT MODIFY this value directly, use {@link #updateSampleCount(int)}, otherwise the modification will not * take effect. *

* Node that this value must be divisor of 1000. */ public static volatile int SAMPLE_COUNT = 2; public static void register2Property(SentinelProperty property) { property.addListener(new SimplePropertyListener() { @Override public void configUpdate(Integer value) { if (value != null) { updateSampleCount(value); } } }); } /** * Update the {@link #SAMPLE_COUNT}. All {@link ClusterNode}s will be reset if newSampleCount * is different from {@link #SAMPLE_COUNT}. * * @param newSampleCount New sample count to set. This value must be divisor of 1000. */ public static void updateSampleCount(int newSampleCount) { if (newSampleCount != SAMPLE_COUNT) { SAMPLE_COUNT = newSampleCount; ClusterBuilderSlot.resetClusterNodes(); } RecordLog.info("SAMPLE_COUNT updated to: {}", SAMPLE_COUNT); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; import com.alibaba.csp.sentinel.slots.statistic.metric.Metric; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.util.function.Predicate; /** *

The statistic node keep three kinds of real-time statistics metrics:

*
    *
  1. metrics in second level ({@code rollingCounterInSecond})
  2. *
  3. metrics in minute level ({@code rollingCounterInMinute})
  4. *
  5. thread count
  6. *
* *

* Sentinel use sliding window to record and count the resource statistics in real-time. * The sliding window infrastructure behind the {@link ArrayMetric} is {@code LeapArray}. *

* *

* case 1: When the first request comes in, Sentinel will create a new window bucket of * a specified time-span to store running statics, such as total response time(rt), * incoming request(QPS), block request(bq), etc. And the time-span is defined by sample count. *

*
 * 	0      100ms
 *  +-------+--→ Sliding Windows
 * 	    ^
 * 	    |
 * 	  request
 * 
*

* Sentinel use the statics of the valid buckets to decide whether this request can be passed. * For example, if a rule defines that only 100 requests can be passed, * it will sum all qps in valid buckets, and compare it to the threshold defined in rule. *

* *

case 2: continuous requests

*
 *  0    100ms    200ms    300ms
 *  +-------+-------+-------+-----→ Sliding Windows
 *                      ^
 *                      |
 *                   request
 * 
* *

case 3: requests keeps coming, and previous buckets become invalid

*
 *  0    100ms    200ms	  800ms	   900ms  1000ms    1300ms
 *  +-------+-------+ ...... +-------+-------+ ...... +-------+-----→ Sliding Windows
 *                                                      ^
 *                                                      |
 *                                                    request
 * 
* *

The sliding window should become:

*
 * 300ms     800ms  900ms  1000ms  1300ms
 *  + ...... +-------+ ...... +-------+-----→ Sliding Windows
 *                                                      ^
 *                                                      |
 *                                                    request
 * 
* * @author qinan.qn * @author jialiang.linjl */ public class StatisticNode implements Node { /** * Holds statistics of the recent {@code INTERVAL} milliseconds. The {@code INTERVAL} is divided into time spans * by given {@code sampleCount}. */ private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); /** * Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds, * meaning each bucket per second, in this way we can get accurate statistics of each second. */ private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false); /** * The counter for thread count. */ private LongAdder curThreadNum = new LongAdder(); /** * The last timestamp when metrics were fetched. */ private long lastFetchTime = -1; @Override public Map metrics() { // The fetch operation is thread-safe under a single-thread scheduler pool. long currentTime = TimeUtil.currentTimeMillis(); currentTime = currentTime - currentTime % 1000; Map metrics = new ConcurrentHashMap<>(); List nodesOfEverySecond = rollingCounterInMinute.details(); long newLastFetchTime = lastFetchTime; // Iterate metrics of all resources, filter valid metrics (not-empty and up-to-date). for (MetricNode node : nodesOfEverySecond) { if (isNodeInTime(node, currentTime) && isValidMetricNode(node)) { metrics.put(node.getTimestamp(), node); newLastFetchTime = Math.max(newLastFetchTime, node.getTimestamp()); } } lastFetchTime = newLastFetchTime; return metrics; } @Override public List rawMetricsInMin(Predicate timePredicate) { return rollingCounterInMinute.detailsOnCondition(timePredicate); } private boolean isNodeInTime(MetricNode node, long currentTime) { return node.getTimestamp() > lastFetchTime && node.getTimestamp() < currentTime; } private boolean isValidMetricNode(MetricNode node) { return node.getPassQps() > 0 || node.getBlockQps() > 0 || node.getSuccessQps() > 0 || node.getExceptionQps() > 0 || node.getRt() > 0 || node.getOccupiedPassQps() > 0; } @Override public void reset() { rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); } @Override public long totalRequest() { return rollingCounterInMinute.pass() + rollingCounterInMinute.block(); } @Override public long blockRequest() { return rollingCounterInMinute.block(); } @Override public double blockQps() { return rollingCounterInSecond.block() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public double previousBlockQps() { return this.rollingCounterInMinute.previousWindowBlock(); } @Override public double previousPassQps() { return this.rollingCounterInMinute.previousWindowPass(); } @Override public double totalQps() { return passQps() + blockQps(); } @Override public long totalSuccess() { return rollingCounterInMinute.success(); } @Override public double exceptionQps() { return rollingCounterInSecond.exception() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public long totalException() { return rollingCounterInMinute.exception(); } @Override public double passQps() { return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public long totalPass() { return rollingCounterInMinute.pass(); } @Override public double successQps() { return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public double maxSuccessQps() { return (double) rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public double occupiedPassQps() { return rollingCounterInSecond.occupiedPass() / rollingCounterInSecond.getWindowIntervalInSec(); } @Override public double avgRt() { long successCount = rollingCounterInSecond.success(); if (successCount == 0) { return 0; } return rollingCounterInSecond.rt() * 1.0 / successCount; } @Override public double minRt() { return rollingCounterInSecond.minRt(); } @Override public int curThreadNum() { return (int)curThreadNum.sum(); } @Override public void addPassRequest(int count) { rollingCounterInSecond.addPass(count); rollingCounterInMinute.addPass(count); } @Override public void addRtAndSuccess(long rt, int successCount) { rollingCounterInSecond.addSuccess(successCount); rollingCounterInSecond.addRT(rt); rollingCounterInMinute.addSuccess(successCount); rollingCounterInMinute.addRT(rt); } @Override public void increaseBlockQps(int count) { rollingCounterInSecond.addBlock(count); rollingCounterInMinute.addBlock(count); } @Override public void increaseExceptionQps(int count) { rollingCounterInSecond.addException(count); rollingCounterInMinute.addException(count); } @Override public void increaseThreadNum() { curThreadNum.increment(); } @Override public void decreaseThreadNum() { curThreadNum.decrement(); } @Override public void debug() { rollingCounterInSecond.debug(); } @Override public long tryOccupyNext(long currentTime, int acquireCount, double threshold) { double maxCount = threshold * IntervalProperty.INTERVAL / 1000; long currentBorrow = rollingCounterInSecond.waiting(); if (currentBorrow >= maxCount) { return OccupyTimeoutProperty.getOccupyTimeout(); } int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT; long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL; int idx = 0; /* * Note: here {@code currentPass} may be less than it really is NOW, because time difference * since call rollingCounterInSecond.pass(). So in high concurrency, the following code may * lead more tokens be borrowed. */ long currentPass = rollingCounterInSecond.pass(); while (earliestTime < currentTime) { long waitInMs = idx * windowLength + windowLength - currentTime % windowLength; if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) { break; } long windowPass = rollingCounterInSecond.getWindowPass(earliestTime); if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) { return waitInMs; } earliestTime += windowLength; currentPass -= windowPass; idx++; } return OccupyTimeoutProperty.getOccupyTimeout(); } @Override public long waiting() { return rollingCounterInSecond.waiting(); } @Override public void addWaitingRequest(long futureTime, int acquireCount) { rollingCounterInSecond.addWaiting(futureTime, acquireCount); } @Override public void addOccupiedPass(int acquireCount) { rollingCounterInMinute.addOccupiedPass(acquireCount); rollingCounterInMinute.addPass(acquireCount); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; /** * Metrics data for a specific resource at given {@code timestamp}. * * @author jialiang.linjl * @author Carpenter Lee */ public class MetricNode { private String resource; /** * Resource classification (e.g. SQL or RPC) * @since 1.7.0 */ private int classification; private long timestamp; private long passQps; private long blockQps; private long successQps; private long exceptionQps; private long rt; /** * @since 1.5.0 */ private long occupiedPassQps; /** * @since 1.7.0 */ private int concurrency; public long getTimestamp() { return timestamp; } public long getOccupiedPassQps() { return occupiedPassQps; } public void setOccupiedPassQps(long occupiedPassQps) { this.occupiedPassQps = occupiedPassQps; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public long getSuccessQps() { return successQps; } public void setSuccessQps(long successQps) { this.successQps = successQps; } public long getPassQps() { return passQps; } public void setPassQps(long passQps) { this.passQps = passQps; } public long getExceptionQps() { return exceptionQps; } public void setExceptionQps(long exceptionQps) { this.exceptionQps = exceptionQps; } public long getBlockQps() { return blockQps; } public void setBlockQps(long blockQps) { this.blockQps = blockQps; } public long getRt() { return rt; } public void setRt(long rt) { this.rt = rt; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public int getClassification() { return classification; } public MetricNode setClassification(int classification) { this.classification = classification; return this; } public int getConcurrency() { return concurrency; } public MetricNode setConcurrency(int concurrency) { this.concurrency = concurrency; return this; } @Override public String toString() { return "MetricNode{" + "resource='" + resource + '\'' + ", classification=" + classification + ", timestamp=" + timestamp + ", passQps=" + passQps + ", blockQps=" + blockQps + ", successQps=" + successQps + ", exceptionQps=" + exceptionQps + ", rt=" + rt + ", concurrency=" + concurrency + ", occupiedPassQps=" + occupiedPassQps + '}'; } /** * To formatting string. All "|" in {@link #resource} will be replaced with * "_", format is:
* * timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps * * * @return string format of this. */ public String toThinString() { StringBuilder sb = new StringBuilder(); sb.append(timestamp).append("|"); String legalName = resource.replaceAll("\\|", "_"); sb.append(legalName).append("|"); sb.append(passQps).append("|"); sb.append(blockQps).append("|"); sb.append(successQps).append("|"); sb.append(exceptionQps).append("|"); sb.append(rt).append("|"); sb.append(occupiedPassQps).append("|"); sb.append(concurrency).append("|"); sb.append(classification); return sb.toString(); } /** * Parse {@link MetricNode} from thin string, see {@link #toThinString()} * * @param line * @return */ public static MetricNode fromThinString(String line) { MetricNode node = new MetricNode(); String[] strs = line.split("\\|"); node.setTimestamp(Long.parseLong(strs[0])); node.setResource(strs[1]); node.setPassQps(Long.parseLong(strs[2])); node.setBlockQps(Long.parseLong(strs[3])); node.setSuccessQps(Long.parseLong(strs[4])); node.setExceptionQps(Long.parseLong(strs[5])); node.setRt(Long.parseLong(strs[6])); if (strs.length >= 8) { node.setOccupiedPassQps(Long.parseLong(strs[7])); } if (strs.length >= 9) { node.setConcurrency(Integer.parseInt(strs[8])); } if (strs.length == 10) { node.setClassification(Integer.parseInt(strs[9])); } return node; } /** * To formatting string. All "|" in {@link MetricNode#resource} will be * replaced with "_", format is:
* * timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps\n * * * @return string format of this. */ private static final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public String toFatString() { StringBuilder sb = new StringBuilder(32); sb.delete(0, sb.length()); long timestamp = getTimestamp(); sb.append(timestamp).append("|"); LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()); sb.append(df.format(dateTime)).append("|"); String legalName = getResource().replaceAll("\\|", "_"); sb.append(legalName).append("|"); sb.append(getPassQps()).append("|"); sb.append(getBlockQps()).append("|"); sb.append(getSuccessQps()).append("|"); sb.append(getExceptionQps()).append("|"); sb.append(getRt()).append("|"); sb.append(getOccupiedPassQps()).append("|"); sb.append(concurrency).append("|"); sb.append(classification); sb.append('\n'); return sb.toString(); } /** * Parse {@link MetricNode} from fat string, see {@link #toFatString()} * * @param line * @return the {@link MetricNode} parsed. */ public static MetricNode fromFatString(String line) { String[] strs = line.split("\\|"); Long time = Long.parseLong(strs[0]); MetricNode node = new MetricNode(); node.setTimestamp(time); node.setResource(strs[2]); node.setPassQps(Long.parseLong(strs[3])); node.setBlockQps(Long.parseLong(strs[4])); node.setSuccessQps(Long.parseLong(strs[5])); node.setExceptionQps(Long.parseLong(strs[6])); node.setRt(Long.parseLong(strs[7])); if (strs.length >= 9) { node.setOccupiedPassQps(Long.parseLong(strs[8])); } if (strs.length >= 10) { node.setConcurrency(Integer.parseInt(strs[9])); } if (strs.length == 11) { node.setClassification(Integer.parseInt(strs[10])); } return node; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricSearcher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.nio.charset.Charset; import java.util.List; import com.alibaba.csp.sentinel.config.SentinelConfig; /** * 从指定目录下找出所有的metric文件,并按照指定时间戳进行检索,参考{@link MetricSearcher#find(long, int)}。 * 会借助索引以提高检索效率,参考{@link MetricWriter};还会在内部缓存上一次检索的文件指针,以便下一次顺序检索时 * 减少读盘次数。 * * @author leyou */ public class MetricSearcher { private static final Charset defaultCharset = Charset.forName(SentinelConfig.charset()); private final MetricsReader metricsReader; private String baseDir; private String baseFileName; private Position lastPosition = new Position(); /** * @param baseDir metric文件所在目录 * @param baseFileName metric文件名的关键字,比如 alihot-metrics.log */ public MetricSearcher(String baseDir, String baseFileName) { this(baseDir, baseFileName, defaultCharset); } /** * @param baseDir metric文件所在目录 * @param baseFileName metric文件名的关键字,比如 alihot-metrics.log * @param charset */ public MetricSearcher(String baseDir, String baseFileName, Charset charset) { if (baseDir == null) { throw new IllegalArgumentException("baseDir can't be null"); } if (baseFileName == null) { throw new IllegalArgumentException("baseFileName can't be null"); } if (charset == null) { throw new IllegalArgumentException("charset can't be null"); } this.baseDir = baseDir; if (!baseDir.endsWith(File.separator)) { this.baseDir += File.separator; } this.baseFileName = baseFileName; metricsReader = new MetricsReader(charset); } /** * 从beginTime开始,检索recommendLines条(大概)记录。同一秒中的数据是原子的,不能分割成多次查询。 * * @param beginTimeMs 检索的最小时间戳 * @param recommendLines 查询最多想得到的记录条数,返回条数会尽可能不超过这个数字。但是为保证每一秒的数据不被分割,有时候 * 返回的记录条数会大于该数字。 * @return * @throws Exception */ public synchronized List find(long beginTimeMs, int recommendLines) throws Exception { List fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName); int i = 0; long offsetInIndex = 0; if (validPosition(beginTimeMs)) { i = fileNames.indexOf(lastPosition.metricFileName); if (i == -1) { i = 0; } else { offsetInIndex = lastPosition.offsetInIndex; } } for (; i < fileNames.size(); i++) { String fileName = fileNames.get(i); long offset = findOffset(beginTimeMs, fileName, MetricWriter.formIndexFileName(fileName), offsetInIndex); offsetInIndex = 0; if (offset != -1) { return metricsReader.readMetrics(fileNames, i, offset, recommendLines); } } return null; } /** * Find metric between [beginTimeMs, endTimeMs], both side inclusive. * When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific * identity will be read. */ public synchronized List findByTimeAndResource(long beginTimeMs, long endTimeMs, String identity) throws Exception { List fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName); //RecordLog.info("pid=" + pid + ", findByTimeAndResource([" + beginTimeMs + ", " + endTimeMs // + "], " + identity + ")"); int i = 0; long offsetInIndex = 0; if (validPosition(beginTimeMs)) { i = fileNames.indexOf(lastPosition.metricFileName); if (i == -1) { i = 0; } else { offsetInIndex = lastPosition.offsetInIndex; } } else { //RecordLog.info("lastPosition is invalidate, will re iterate all files, pid = " + pid); } for (; i < fileNames.size(); i++) { String fileName = fileNames.get(i); long offset = findOffset(beginTimeMs, fileName, MetricWriter.formIndexFileName(fileName), offsetInIndex); offsetInIndex = 0; if (offset != -1) { return metricsReader.readMetricsByEndTime(fileNames, i, offset, beginTimeMs, endTimeMs, identity); } } return null; } /** * 记录上一次读取的index文件位置和数值 */ private static final class Position { String metricFileName; String indexFileName; /** * 索引文件内的偏移 */ long offsetInIndex; /** * 索引文件中offsetInIndex位置上的数字,秒数。 */ long second; } /** * The position we cached is useful only when {@code beginTimeMs} is >= {@code lastPosition.second} * and the index file exists and the second we cached is same as in the index file. */ private boolean validPosition(long beginTimeMs) { if (beginTimeMs / 1000 < lastPosition.second) { return false; } if (lastPosition.indexFileName == null) { return false; } // index file dose not exits if (!new File(lastPosition.indexFileName).exists()) { return false; } FileInputStream in = null; try { in = new FileInputStream(lastPosition.indexFileName); in.getChannel().position(lastPosition.offsetInIndex); DataInputStream indexIn = new DataInputStream(in); // timestamp(second) in the specific position == that we cached return indexIn.readLong() == lastPosition.second; } catch (Exception e) { return false; } finally { if (in != null) { try { in.close(); } catch (Exception ignore) { } } } } private long findOffset(long beginTime, String metricFileName, String idxFileName, long offsetInIndex) throws Exception { lastPosition.metricFileName = null; lastPosition.indexFileName = null; if (!new File(idxFileName).exists()) { return -1; } long beginSecond = beginTime / 1000; FileInputStream in = new FileInputStream(idxFileName); in.getChannel().position(offsetInIndex); DataInputStream indexIn = new DataInputStream(in); long offset; try { long second; lastPosition.offsetInIndex = in.getChannel().position(); while ((second = indexIn.readLong()) < beginSecond) { offset = indexIn.readLong(); lastPosition.offsetInIndex = in.getChannel().position(); } offset = indexIn.readLong(); lastPosition.metricFileName = metricFileName; lastPosition.indexFileName = idxFileName; lastPosition.second = second; return offset; } catch (EOFException ignore) { return -1; } finally { indexIn.close(); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** * @author jialiang.linjl */ public class MetricTimerListener implements Runnable { private static final MetricWriter metricWriter = new MetricWriter(SentinelConfig.singleMetricFileSize(), SentinelConfig.totalMetricFileCount()); @Override public void run() { Map> maps = new TreeMap<>(); for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { ClusterNode node = e.getValue(); Map metrics = node.metrics(); aggregate(maps, metrics, node); } aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.ENTRY_NODE); if (!maps.isEmpty()) { for (Entry> entry : maps.entrySet()) { try { metricWriter.write(entry.getKey(), entry.getValue()); } catch (Exception e) { RecordLog.warn("[MetricTimerListener] Write metric error", e); } } } } private void aggregate(Map> maps, Map metrics, ClusterNode node) { for (Entry entry : metrics.entrySet()) { long time = entry.getKey(); MetricNode metricNode = entry.getValue(); metricNode.setResource(node.getName()); metricNode.setClassification(node.getResourceType()); maps.computeIfAbsent(time, k -> new ArrayList()); List nodes = maps.get(time); nodes.add(entry.getValue()); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricWriter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.util.PidUtil; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; /** * This class is responsible for writing {@link MetricNode} to disk: *
    *
  1. metric with the same second should write to the same file;
  2. *
  3. single file size must be controlled;
  4. *
  5. file name is like: {@code ${appName}-metrics.log.pid${pid}.yyyy-MM-dd.[number]}
  6. *
  7. metric of different day should in different file;
  8. *
  9. every metric file is accompanied with an index file, which file name is {@code ${metricFileName}.idx}
  10. *
* * @author Carpenter Lee */ public class MetricWriter { private static final String CHARSET = SentinelConfig.charset(); public static final String METRIC_BASE_DIR = LogBase.getLogBaseDir(); /** * Note: {@link MetricFileNameComparator}'s implementation relies on the metric file name, * so we should be careful when changing the metric file name. * * @see #formMetricFileName(String, int) */ public static final String METRIC_FILE = "metrics.log"; public static final String METRIC_FILE_INDEX_SUFFIX = ".idx"; public static final Comparator METRIC_FILE_NAME_CMP = new MetricFileNameComparator(); private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 排除时差干扰 */ private long timeSecondBase; private String baseDir; private String baseFileName; /** * file must exist when writing */ private File curMetricFile; private File curMetricIndexFile; private FileOutputStream outMetric; private DataOutputStream outIndex; private BufferedOutputStream outMetricBuf; private long singleFileSize; private int totalFileCount; private boolean append = false; private final int pid = PidUtil.getPid(); /** * 秒级统计,忽略毫秒数。 */ private long lastSecond = -1; public MetricWriter(long singleFileSize) { this(singleFileSize, 6); } public MetricWriter(long singleFileSize, int totalFileCount) { if (singleFileSize <= 0 || totalFileCount <= 0) { throw new IllegalArgumentException(); } RecordLog.info("[MetricWriter] Creating new MetricWriter, singleFileSize={}, totalFileCount={}", singleFileSize, totalFileCount); this.baseDir = METRIC_BASE_DIR; File dir = new File(baseDir); if (!dir.exists()) { dir.mkdirs(); } long time = System.currentTimeMillis(); this.lastSecond = time / 1000; this.singleFileSize = singleFileSize; this.totalFileCount = totalFileCount; try { this.timeSecondBase = df.parse("1970-01-01 00:00:00").getTime() / 1000; } catch (Exception e) { RecordLog.warn("[MetricWriter] Create new MetricWriter error", e); } } /** * 如果传入了time,就认为nodes中所有的时间时间戳都是time. * * @param time * @param nodes */ public synchronized void write(long time, List nodes) throws Exception { if (nodes == null) { return; } for (MetricNode node : nodes) { node.setTimestamp(time); } String appName = SentinelConfig.getAppName(); if (appName == null) { appName = ""; } // first write, should create file if (curMetricFile == null) { baseFileName = formMetricFileName(appName, pid); closeAndNewFile(nextFileNameOfDay(time)); } if (!(curMetricFile.exists() && curMetricIndexFile.exists())) { closeAndNewFile(nextFileNameOfDay(time)); } long second = time / 1000; if (second < lastSecond) { // 时间靠前的直接忽略,不应该发生。 } else if (second == lastSecond) { for (MetricNode node : nodes) { outMetricBuf.write(node.toFatString().getBytes(CHARSET)); } outMetricBuf.flush(); if (!validSize()) { closeAndNewFile(nextFileNameOfDay(time)); } } else { writeIndex(second, outMetric.getChannel().position()); if (isNewDay(lastSecond, second)) { closeAndNewFile(nextFileNameOfDay(time)); for (MetricNode node : nodes) { outMetricBuf.write(node.toFatString().getBytes(CHARSET)); } outMetricBuf.flush(); if (!validSize()) { closeAndNewFile(nextFileNameOfDay(time)); } } else { for (MetricNode node : nodes) { outMetricBuf.write(node.toFatString().getBytes(CHARSET)); } outMetricBuf.flush(); if (!validSize()) { closeAndNewFile(nextFileNameOfDay(time)); } } lastSecond = second; } } public synchronized void close() throws Exception { if (outMetricBuf != null) { outMetricBuf.close(); } if (outIndex != null) { outIndex.close(); } } private void writeIndex(long time, long offset) throws Exception { outIndex.writeLong(time); outIndex.writeLong(offset); outIndex.flush(); } private String nextFileNameOfDay(long time) { List list = new ArrayList(); File baseFile = new File(baseDir); DateFormat fileNameDf = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = fileNameDf.format(new Date(time)); String fileNameModel = baseFileName + "." + dateStr; for (File file : baseFile.listFiles()) { String fileName = file.getName(); if (fileName.contains(fileNameModel) && !fileName.endsWith(METRIC_FILE_INDEX_SUFFIX) && !fileName.endsWith(".lck")) { list.add(file.getAbsolutePath()); } } Collections.sort(list, METRIC_FILE_NAME_CMP); if (list.isEmpty()) { return baseDir + fileNameModel; } String last = list.get(list.size() - 1); int n = 0; String[] strs = last.split("\\."); if (strs.length > 0 && strs[strs.length - 1].matches("[0-9]{1,10}")) { n = Integer.parseInt(strs[strs.length - 1]); } return baseDir + fileNameModel + "." + (n + 1); } /** * A comparator for metric file name. Metric file name is like:
*
     * metrics.log.2018-03-06
     * metrics.log.2018-03-07
     * metrics.log.2018-03-07.10
     * metrics.log.2018-03-06.100
     * 
*

* File name with the early date is smaller, if date is same, the one with the small file number is smaller. * Note that if the name is an absolute path, only the fileName({@link File#getName()}) part will be considered. * So the above file names should be sorted as:
*

     * metrics.log.2018-03-06
     * metrics.log.2018-03-06.100
     * metrics.log.2018-03-07
     * metrics.log.2018-03-07.10
     *
     * 
*

*/ private static final class MetricFileNameComparator implements Comparator { private final String pid = "pid"; @Override public int compare(String o1, String o2) { String name1 = new File(o1).getName(); String name2 = new File(o2).getName(); String dateStr1 = name1.split("\\.")[2]; String dateStr2 = name2.split("\\.")[2]; // in case of file name contains pid, skip it, like Sentinel-Admin-metrics.log.pid22568.2018-12-24 if (dateStr1.startsWith(pid)) { dateStr1 = name1.split("\\.")[3]; dateStr2 = name2.split("\\.")[3]; } // compare date first int t = dateStr1.compareTo(dateStr2); if (t != 0) { return t; } // same date, compare file number t = name1.length() - name2.length(); if (t != 0) { return t; } return name1.compareTo(name2); } } /** * Get all metric files' name in {@code baseDir}. The file name must like *
     * baseFileName + ".yyyy-MM-dd.number"
     * 
* and not endsWith {@link #METRIC_FILE_INDEX_SUFFIX} or ".lck". * * @param baseDir the directory to search. * @param baseFileName the file name pattern. * @return the metric files' absolute path({@link File#getAbsolutePath()}) * @throws Exception */ static List listMetricFiles(String baseDir, String baseFileName) throws Exception { List list = new ArrayList(); File baseFile = new File(baseDir); File[] files = baseFile.listFiles(); if (files == null) { return list; } for (File file : files) { String fileName = file.getName(); if (file.isFile() && fileNameMatches(fileName, baseFileName) && !fileName.endsWith(MetricWriter.METRIC_FILE_INDEX_SUFFIX) && !fileName.endsWith(".lck")) { list.add(file.getAbsolutePath()); } } Collections.sort(list, MetricWriter.METRIC_FILE_NAME_CMP); return list; } /** * Test whether fileName matches baseFileName. fileName matches baseFileName when *
     * fileName = baseFileName + ".yyyy-MM-dd.number"
     * 
* * @param fileName file name * @param baseFileName base file name. * @return if fileName matches baseFileName return true, else return false. */ public static boolean fileNameMatches(String fileName, String baseFileName) { if (fileName.startsWith(baseFileName)) { String part = fileName.substring(baseFileName.length()); // part is like: ".yyyy-MM-dd.number", eg. ".2018-12-24.11" return part.matches("\\.[0-9]{4}-[0-9]{2}-[0-9]{2}(\\.[0-9]*)?"); } else { return false; } } private void removeMoreFiles() throws Exception { List list = listMetricFiles(baseDir, baseFileName); if (list == null || list.isEmpty()) { return; } for (int i = 0; i < list.size() - totalFileCount + 1; i++) { String fileName = list.get(i); String indexFile = formIndexFileName(fileName); new File(fileName).delete(); RecordLog.info("[MetricWriter] Removing metric file: {}", fileName); new File(indexFile).delete(); RecordLog.info("[MetricWriter] Removing metric index file: {}", indexFile); } } private void closeAndNewFile(String fileName) throws Exception { removeMoreFiles(); if (outMetricBuf != null) { outMetricBuf.close(); } if (outIndex != null) { outIndex.close(); } outMetric = new FileOutputStream(fileName, append); outMetricBuf = new BufferedOutputStream(outMetric); curMetricFile = new File(fileName); String idxFile = formIndexFileName(fileName); curMetricIndexFile = new File(idxFile); outIndex = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile, append))); RecordLog.info("[MetricWriter] New metric file created: {}", fileName); RecordLog.info("[MetricWriter] New metric index file created: {}", idxFile); } private boolean validSize() throws Exception { long size = outMetric.getChannel().size(); return size < singleFileSize; } private boolean isNewDay(long lastSecond, long second) { long lastDay = (lastSecond - timeSecondBase) / 86400; long newDay = (second - timeSecondBase) / 86400; return newDay > lastDay; } /** * Form metric file name use the specific appName and pid. Note that only * form the file name, not include path. * * Note: {@link MetricFileNameComparator}'s implementation relays on the metric file name, * we should be careful when changing the metric file name. * * @param appName * @param pid * @return metric file name. */ public static String formMetricFileName(String appName, int pid) { if (appName == null) { appName = ""; } // dot is special char that should be replaced. final String dot = "."; final String separator = "-"; if (appName.contains(dot)) { appName = appName.replace(dot, separator); } String name = appName + separator + METRIC_FILE; if (LogBase.isLogNameUsePid()) { name += ".pid" + pid; } return name; } /** * Form index file name of the {@code metricFileName} * * @param metricFileName * @return the index file name of the metricFileName */ public static String formIndexFileName(String metricFileName) { return metricFileName + METRIC_FILE_INDEX_SUFFIX; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricsReader.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * Reads metrics data from log file. */ class MetricsReader { /** * Avoid OOM in any cases. */ private static final int MAX_LINES_RETURN = 100000; private final Charset charset; public MetricsReader(Charset charset) { this.charset = charset; } /** * @return if should continue read, return true, else false. */ boolean readMetricsInOneFileByEndTime(List list, String fileName, long offset, long beginTimeMs, long endTimeMs, String identity) throws Exception { FileInputStream in = null; long beginSecond = beginTimeMs / 1000; long endSecond = endTimeMs / 1000; try { in = new FileInputStream(fileName); in.getChannel().position(offset); BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset)); String line; while ((line = reader.readLine()) != null) { MetricNode node = MetricNode.fromFatString(line); long currentSecond = node.getTimestamp() / 1000; // currentSecond should >= beginSecond, otherwise a wrong metric file must occur if (currentSecond < beginSecond) { return false; } if (currentSecond <= endSecond) { // read all if (identity == null) { list.add(node); } else if (node.getResource().equals(identity)) { list.add(node); } } else { return false; } if (list.size() >= MAX_LINES_RETURN) { return false; } } } finally { if (in != null) { in.close(); } } return true; } void readMetricsInOneFile(List list, String fileName, long offset, int recommendLines) throws Exception { //if(list.size() >= recommendLines){ // return; //} long lastSecond = -1; if (list.size() > 0) { lastSecond = list.get(list.size() - 1).getTimestamp() / 1000; } FileInputStream in = null; try { in = new FileInputStream(fileName); in.getChannel().position(offset); BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset)); String line; while ((line = reader.readLine()) != null) { MetricNode node = MetricNode.fromFatString(line); long currentSecond = node.getTimestamp() / 1000; if (list.size() < recommendLines) { list.add(node); } else if (currentSecond == lastSecond) { list.add(node); } else { break; } lastSecond = currentSecond; } } finally { if (in != null) { in.close(); } } } /** * When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific * identity will be read. */ List readMetricsByEndTime(List fileNames, int pos, long offset, long beginTimeMs, long endTimeMs, String identity) throws Exception { List list = new ArrayList(1024); if (readMetricsInOneFileByEndTime(list, fileNames.get(pos++), offset, beginTimeMs, endTimeMs, identity)) { while (pos < fileNames.size() && readMetricsInOneFileByEndTime(list, fileNames.get(pos++), 0, beginTimeMs, endTimeMs, identity)) { } } return list; } List readMetrics(List fileNames, int pos, long offset, int recommendLines) throws Exception { List list = new ArrayList(recommendLines); readMetricsInOneFile(list, fileNames.get(pos++), offset, recommendLines); while (list.size() < recommendLines && pos < fileNames.size()) { readMetricsInOneFile(list, fileNames.get(pos++), 0, recommendLines); } return list; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.property; import com.alibaba.csp.sentinel.log.RecordLog; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; public class DynamicSentinelProperty implements SentinelProperty { protected Set> listeners = new CopyOnWriteArraySet<>(); private T value = null; public DynamicSentinelProperty() { } public DynamicSentinelProperty(T value) { super(); this.value = value; } @Override public void addListener(PropertyListener listener) { listeners.add(listener); listener.configLoad(value); } @Override public void removeListener(PropertyListener listener) { listeners.remove(listener); } @Override public boolean updateValue(T newValue) { if (isEqual(value, newValue)) { return false; } RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue); value = newValue; for (PropertyListener listener : listeners) { listener.configUpdate(newValue); } return true; } private boolean isEqual(T oldValue, T newValue) { if (oldValue == null && newValue == null) { return true; } if (oldValue == null) { return false; } return oldValue.equals(newValue); } public void close() { listeners.clear(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.property; /** * A {@link SentinelProperty} that will never inform the {@link PropertyListener} on it. * * @author leyou */ public final class NoOpSentinelProperty implements SentinelProperty { @Override public void addListener(PropertyListener listener) { } @Override public void removeListener(PropertyListener listener) { } @Override public boolean updateValue(Object newValue) { return true; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/PropertyListener.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.property; /** * This class holds callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener * * @author jialiang.linjl */ public interface PropertyListener { /** * Callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener. * * @param value updated value. */ void configUpdate(T value); /** * The first time of the {@code value}'s load. * * @param value the value loaded. */ void configLoad(T value); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/SentinelProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.property; /** *

* This class holds current value of the config, and is responsible for informing all {@link PropertyListener}s * added on this when the config is updated. *

*

* Note that not every {@link #updateValue(Object newValue)} invocation should inform the listeners, only when * {@code newValue} is not Equals to the old value, informing is needed. *

* * @param the target type. * @author Carpenter Lee */ public interface SentinelProperty { /** *

* Add a {@link PropertyListener} to this {@link SentinelProperty}. After the listener is added, * {@link #updateValue(Object)} will inform the listener if needed. *

*

* This method can invoke multi times to add more than one listeners. *

* * @param listener listener to add. */ void addListener(PropertyListener listener); /** * Remove the {@link PropertyListener} on this. After removing, {@link #updateValue(Object)} * will not inform the listener. * * @param listener the listener to remove. */ void removeListener(PropertyListener listener); /** * Update the {@code newValue} as the current value of this property and inform all {@link PropertyListener}s * added on this only when new {@code newValue} is not Equals to the old value. * * @param newValue the new value. * @return true if the value in property has been updated, otherwise false */ boolean updateValue(T newValue); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/SimplePropertyListener.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.property; public abstract class SimplePropertyListener implements PropertyListener { @Override public void configLoad(T value) { configUpdate(value); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.context.Context; /** * @author qinan.qn * @author jialiang.linjl */ public abstract class AbstractLinkedProcessorSlot implements ProcessorSlot { private AbstractLinkedProcessorSlot next = null; @Override public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { if (next != null) { next.transformEntry(context, resourceWrapper, obj, count, prioritized, args); } } @SuppressWarnings("unchecked") void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args) throws Throwable { T t = (T)o; entry(context, resourceWrapper, t, count, prioritized, args); } @Override public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { if (next != null) { next.exit(context, resourceWrapper, count, args); } } public AbstractLinkedProcessorSlot getNext() { return next; } public void setNext(AbstractLinkedProcessorSlot next) { this.next = next; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.context.Context; /** * @author qinan.qn * @author jialiang.linjl */ public class DefaultProcessorSlotChain extends ProcessorSlotChain { AbstractLinkedProcessorSlot first = new AbstractLinkedProcessorSlot() { @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) throws Throwable { super.fireEntry(context, resourceWrapper, t, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { super.fireExit(context, resourceWrapper, count, args); } }; AbstractLinkedProcessorSlot end = first; @Override public void addFirst(AbstractLinkedProcessorSlot protocolProcessor) { protocolProcessor.setNext(first.getNext()); first.setNext(protocolProcessor); if (end == first) { end = protocolProcessor; } } @Override public void addLast(AbstractLinkedProcessorSlot protocolProcessor) { end.setNext(protocolProcessor); end = protocolProcessor; } /** * Same as {@link #addLast(AbstractLinkedProcessorSlot)}. * * @param next processor to be added. */ @Override public void setNext(AbstractLinkedProcessorSlot next) { addLast(next); } @Override public AbstractLinkedProcessorSlot getNext() { return first.getNext(); } @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) throws Throwable { first.transformEntry(context, resourceWrapper, t, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { first.exit(context, resourceWrapper, count, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import java.lang.reflect.Method; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.util.IdUtil; import com.alibaba.csp.sentinel.util.MethodUtil; /** * Resource wrapper for method invocation. * * @author qinan.qn */ public class MethodResourceWrapper extends ResourceWrapper { private final transient Method method; public MethodResourceWrapper(Method method, EntryType e) { this(method, e, ResourceTypeConstants.COMMON); } public MethodResourceWrapper(Method method, EntryType e, int resType) { super(MethodUtil.resolveMethodName(method), e, resType); this.method = method; } public Method getMethod() { return method; } @Override public String getShowName() { return name; } @Override public String toString() { return "MethodResourceWrapper{" + "name='" + name + '\'' + ", entryType=" + entryType + ", resourceType=" + resourceType + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.context.Context; /** * A container of some process and ways of notification when the process is finished. * * @author qinan.qn * @author jialiang.linjl * @author leyou(lihao) * @author Eric Zhao */ public interface ProcessorSlot { /** * Entrance of this slot. * * @param context current {@link Context} * @param resourceWrapper current resource * @param param generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node} * @param count tokens needed * @param prioritized whether the entry is prioritized * @param args parameters of the original call * @throws Throwable blocked exception or unexpected error */ void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized, Object... args) throws Throwable; /** * Means finish of {@link #entry(Context, ResourceWrapper, Object, int, boolean, Object...)}. * * @param context current {@link Context} * @param resourceWrapper current resource * @param obj relevant object (e.g. Node) * @param count tokens needed * @param prioritized whether the entry is prioritized * @param args parameters of the original call * @throws Throwable blocked exception or unexpected error */ void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable; /** * Exit of this slot. * * @param context current {@link Context} * @param resourceWrapper current resource * @param count tokens needed * @param args parameters of the original call */ void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); /** * Means finish of {@link #exit(Context, ResourceWrapper, int, Object...)}. * * @param context current {@link Context} * @param resourceWrapper current resource * @param count tokens needed * @param args parameters of the original call */ void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; /** * Link all processor slots as a chain. * * @author qinan.qn */ public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot { /** * Add a processor to the head of this slot chain. * * @param protocolProcessor processor to be added. */ public abstract void addFirst(AbstractLinkedProcessorSlot protocolProcessor); /** * Add a processor to the tail of this slot chain. * * @param protocolProcessor processor to be added. */ public abstract void addLast(AbstractLinkedProcessorSlot protocolProcessor); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). * * @author Eric Zhao * @since 0.2.0 */ public interface ProcessorSlotEntryCallback { void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception; void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.context.Context; /** * Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). * * @author Eric Zhao * @since 0.2.0 */ public interface ProcessorSlotExitCallback { void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.util.AssertUtil; /** * A wrapper of resource name and type. * * @author qinan.qn * @author jialiang.linjl * @author Eric Zhao */ public abstract class ResourceWrapper { protected final String name; protected final EntryType entryType; protected final int resourceType; public ResourceWrapper(String name, EntryType entryType, int resourceType) { AssertUtil.notEmpty(name, "resource name cannot be empty"); AssertUtil.notNull(entryType, "entryType cannot be null"); this.name = name; this.entryType = entryType; this.resourceType = resourceType; } /** * Get the resource name. * * @return the resource name */ public String getName() { return name; } /** * Get {@link EntryType} of this wrapper. * * @return {@link EntryType} of this wrapper. */ public EntryType getEntryType() { return entryType; } /** * Get the classification of this resource. * * @return the classification of this resource * @since 1.7.0 */ public int getResourceType() { return resourceType; } /** * Get the beautified resource name to be showed. * * @return the beautified resource name */ public abstract String getShowName(); /** * Only {@link #getName()} is considered. */ @Override public int hashCode() { return getName().hashCode(); } /** * Only {@link #getName()} is considered. */ @Override public boolean equals(Object obj) { if (obj instanceof ResourceWrapper) { ResourceWrapper rw = (ResourceWrapper)obj; return rw.getName().equals(getName()); } return false; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; /** * The builder for processor slot chain. * * @author qinan.qn * @author leyou * @author Eric Zhao */ public interface SlotChainBuilder { /** * Build the processor slot chain. * * @return a processor slot that chain some slots together */ ProcessorSlotChain build(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * A provider for creating slot chains via resolved slot chain builder SPI. * * @author Eric Zhao * @since 0.2.0 */ public final class SlotChainProvider { private static volatile SlotChainBuilder slotChainBuilder = null; /** * The load and pick process is not thread-safe, but it's okay since the method should be only invoked * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock. * * @return new created slot chain */ public static ProcessorSlotChain newSlotChain() { if (slotChainBuilder != null) { return slotChainBuilder.build(); } // Resolve the slot chain builder SPI. slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); if (slotChainBuilder == null) { // Should not go through here. RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default"); slotChainBuilder = new DefaultSlotChainBuilder(); } else { RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}", slotChainBuilder.getClass().getCanonicalName()); } return slotChainBuilder.build(); } private SlotChainProvider() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slotchain; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.ResourceTypeConstants; /** * Common string resource wrapper. * * @author qinan.qn * @author jialiang.linjl */ public class StringResourceWrapper extends ResourceWrapper { public StringResourceWrapper(String name, EntryType e) { super(name, e, ResourceTypeConstants.COMMON); } public StringResourceWrapper(String name, EntryType e, int resType) { super(name, e, resType); } @Override public String getShowName() { return name; } @Override public String toString() { return "StringResourceWrapper{" + "name='" + name + '\'' + ", entryType=" + entryType + ", resourceType=" + resourceType + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.spi.SpiLoader; import java.util.List; /** * Builder for a default {@link ProcessorSlotChain}. * * @author qinan.qn * @author leyou */ @Spi(isDefault = true) public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); List sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); for (ProcessorSlot slot : sortedSlotList) { if (!(slot instanceof AbstractLinkedProcessorSlot)) { RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); continue; } chain.addLast((AbstractLinkedProcessorSlot) slot); } return chain; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; import java.util.Objects; /** * Abstract rule entity. * * @author youji.zj * @author Eric Zhao */ public abstract class AbstractRule implements Rule { /** * rule id. */ private Long id; /** * Resource name. */ private String resource; /** *

* Application name that will be limited by origin. * The default limitApp is {@code default}, which means allowing all origin apps. *

*

* For authority rules, multiple origin name can be separated with comma (','). *

*/ private String limitApp; /** * Whether to match resource names according to regular rules */ private boolean regex; public Long getId() { return id; } public AbstractRule setId(Long id) { this.id = id; return this; } @Override public String getResource() { return resource; } public AbstractRule setResource(String resource) { this.resource = resource; return this; } public String getLimitApp() { return limitApp; } public AbstractRule setLimitApp(String limitApp) { this.limitApp = limitApp; return this; } public boolean isRegex() { return regex; } public AbstractRule setRegex(boolean regex) { this.regex = regex; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AbstractRule)) { return false; } AbstractRule that = (AbstractRule)o; if (!Objects.equals(resource, that.resource)) { return false; } if (regex != that.regex) { return false; } if (!limitAppEquals(limitApp, that.limitApp)) { return false; } return true; } private boolean limitAppEquals(String str1, String str2) { if ("".equals(str1)) { return RuleConstant.LIMIT_APP_DEFAULT.equals(str2); } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(str1)) { return "".equals(str2) || str2 == null || str1.equals(str2); } if (str1 == null) { return str2 == null || RuleConstant.LIMIT_APP_DEFAULT.equals(str2); } return str1.equals(str2); } public T as(Class clazz) { return (T)this; } @Override public int hashCode() { int result = resource != null ? resource.hashCode() : 0; if (!("".equals(limitApp) || RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp) || limitApp == null)) { result = 31 * result + limitApp.hashCode(); } result = 31 * result + (regex ? 1 : 0); return result; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/BlockException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; /** * Abstract exception indicating blocked by Sentinel due to flow control, * circuit breaking or system protection triggered. * * @author youji.zj */ public abstract class BlockException extends Exception { private static final int MAX_SEARCH_DEPTH = 10; public static final String BLOCK_EXCEPTION_FLAG = "SentinelBlockException"; public static final String BLOCK_EXCEPTION_MSG_PREFIX = "SentinelBlockException: "; /** *

this constant RuntimeException has no stack trace, just has a message * {@link #BLOCK_EXCEPTION_FLAG} that marks its name. *

*

* Use {@link #isBlockException(Throwable)} to check whether one Exception * Sentinel Blocked Exception. *

*/ public static RuntimeException THROW_OUT_EXCEPTION = new RuntimeException(BLOCK_EXCEPTION_FLAG); public static StackTraceElement[] sentinelStackTrace = new StackTraceElement[] { new StackTraceElement(BlockException.class.getName(), "block", "BlockException", 0) }; static { THROW_OUT_EXCEPTION.setStackTrace(sentinelStackTrace); } protected AbstractRule rule; private String ruleLimitApp; public BlockException(String ruleLimitApp) { super(); this.ruleLimitApp = ruleLimitApp; } public BlockException(String ruleLimitApp, AbstractRule rule) { super(); this.ruleLimitApp = ruleLimitApp; this.rule = rule; } public BlockException(String message, Throwable cause) { super(message, cause); } public BlockException(String ruleLimitApp, String message) { super(message); this.ruleLimitApp = ruleLimitApp; } public BlockException(String ruleLimitApp, String message, AbstractRule rule) { super(message); this.ruleLimitApp = ruleLimitApp; this.rule = rule; } @Override public Throwable fillInStackTrace() { return this; } public String getRuleLimitApp() { return ruleLimitApp; } public void setRuleLimitApp(String ruleLimitApp) { this.ruleLimitApp = ruleLimitApp; } public RuntimeException toRuntimeException() { RuntimeException t = new RuntimeException(BLOCK_EXCEPTION_MSG_PREFIX + getClass().getSimpleName()); t.setStackTrace(sentinelStackTrace); return t; } /** * Check whether the exception is sentinel blocked exception. One exception is sentinel blocked * exception only when: *
    *
  • the exception or its (sub-)cause is {@link BlockException}, or
  • *
  • the exception's message or any of its sub-cause's message is prefixed by {@link #BLOCK_EXCEPTION_FLAG}
  • *
* * @param t the exception. * @return return true if the exception marks sentinel blocked exception. */ public static boolean isBlockException(Throwable t) { if (null == t) { return false; } int counter = 0; Throwable cause = t; while (cause != null && counter++ < MAX_SEARCH_DEPTH) { if (cause instanceof BlockException) { return true; } if (cause.getMessage() != null && cause.getMessage().startsWith(BLOCK_EXCEPTION_FLAG)) { return true; } cause = cause.getCause(); } return false; } public AbstractRule getRule() { return rule; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; /** * @author Eric Zhao * @since 1.4.0 */ public final class ClusterRuleConstant { public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0; public static final int FLOW_CLUSTER_STRATEGY_BORROW_REF = 1; public static final int FLOW_THRESHOLD_AVG_LOCAL = 0; public static final int FLOW_THRESHOLD_GLOBAL = 1; public static final int DEFAULT_CLUSTER_SAMPLE_COUNT = 10; private ClusterRuleConstant() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/Rule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; /** * Base interface of all rules. * * @author youji.zj */ public interface Rule { /** * Get target resource of this rule. * * @return target resource of this rule */ String getResource(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; import com.alibaba.csp.sentinel.node.IntervalProperty; /** * @author youji.zj * @author jialiang.linjl */ public final class RuleConstant { public static final int FLOW_GRADE_THREAD = 0; public static final int FLOW_GRADE_QPS = 1; public static final int DEGRADE_GRADE_RT = 0; /** * Degrade by biz exception ratio in the current {@link IntervalProperty#INTERVAL} second(s). */ public static final int DEGRADE_GRADE_EXCEPTION_RATIO = 1; /** * Degrade by biz exception count in the last 60 seconds. */ public static final int DEGRADE_GRADE_EXCEPTION_COUNT = 2; public static final int DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT = 5; public static final int DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT = 5; public static final int AUTHORITY_WHITE = 0; public static final int AUTHORITY_BLACK = 1; public static final int STRATEGY_DIRECT = 0; public static final int STRATEGY_RELATE = 1; public static final int STRATEGY_CHAIN = 2; public static final int CONTROL_BEHAVIOR_DEFAULT = 0; public static final int CONTROL_BEHAVIOR_WARM_UP = 1; public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2; public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3; public static final int DEFAULT_BLOCK_STRATEGY = 0; public static final int TRY_AGAIN_BLOCK_STRATEGY = 1; public static final int TRY_UNTIL_SUCCESS_BLOCK_STRATEGY = 2; public static final int DEFAULT_RESOURCE_TIMEOUT_STRATEGY = 0; public static final int RELEASE_RESOURCE_TIMEOUT_STRATEGY = 1; public static final int KEEP_RESOURCE_TIMEOUT_STRATEGY = 2; public static final String LIMIT_APP_DEFAULT = "default"; public static final String LIMIT_APP_OTHER = "other"; public static final int DEFAULT_SAMPLE_COUNT = 2; public static final int DEFAULT_WINDOW_INTERVAL_MS = 1000; private RuleConstant() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; import com.alibaba.csp.sentinel.config.SentinelConfig; import java.util.*; import java.util.regex.Pattern; /** * Unified rule management tool, mainly used for matching and caching of regular rules and simple rules. * @author quguai * @date 2023/10/9 20:35 */ public class RuleManager { private Map> originalRules = new HashMap<>(); private Map> regexRules = new HashMap<>(); private Map> regexCacheRules = new HashMap<>(); private Map> simpleRules = new HashMap<>(); private Function, List> generator = Function.identity(); private final Predicate predicate; public RuleManager() { predicate = r -> r instanceof AbstractRule && ((AbstractRule) r).isRegex(); } public RuleManager(Function, List> generator, Predicate predicate) { this.generator = generator; this.predicate = predicate; } /** * Update rules from datasource, split rules map by regex, * rebuild the regex rule cache to reduce the performance loss caused by publish rules. * * @param rulesMap origin rules map */ public void updateRules(Map> rulesMap) { originalRules = rulesMap; Map> regexRules = new HashMap<>(); Map> simpleRules = new HashMap<>(); for (Map.Entry> entry : rulesMap.entrySet()) { String resource = entry.getKey(); List rules = entry.getValue(); List rulesOfSimple = new ArrayList<>(); List rulesOfRegex = new ArrayList<>(); for (R rule : rules) { if (predicate.test(rule)) { rulesOfRegex.add(rule); } else { rulesOfSimple.add(rule); } } if (!rulesOfRegex.isEmpty()) { regexRules.put(Pattern.compile(resource), rulesOfRegex); } if (!rulesOfSimple.isEmpty()) { simpleRules.put(resource, rulesOfSimple); } } // rebuild regex cache rules setRules(regexRules, simpleRules); } /** * Get rules by resource name, save the rule list after regular matching to improve performance * @param resource resource name * @return matching rule list */ public List getRules(String resource) { List result = new ArrayList<>(simpleRules.getOrDefault(resource, Collections.emptyList())); if (regexRules.isEmpty() || (SentinelConfig.shouldSkipRegexIfSimpleRuleMatched() && !result.isEmpty())) { return result; } if (regexCacheRules.containsKey(resource)) { result.addAll(regexCacheRules.get(resource)); return result; } synchronized (this) { if (regexCacheRules.containsKey(resource)) { result.addAll(regexCacheRules.get(resource)); return result; } List compilers = matcherFromRegexRules(resource); regexCacheRules.put(resource, compilers); result.addAll(compilers); return result; } } /** * Get rules from regex rules and simple rules * @return rule list */ public List getRules() { List rules = new ArrayList<>(); for (Map.Entry> entry : regexRules.entrySet()) { rules.addAll(entry.getValue()); } for (Map.Entry> entry : simpleRules.entrySet()) { rules.addAll(entry.getValue()); } return rules; } /** * Get origin rules, includes regex and simple rules * @return original rules */ public Map> getOriginalRules() { return originalRules; } /** * Determine whether has rule based on the resource name * @param resource resource name * @return whether */ public boolean hasConfig(String resource) { if (resource == null) { return false; } return !getRules(resource).isEmpty(); } /** * Is valid regex rules * @param rule rule * @return weather valid regex rule */ public static boolean checkRegexResourceField(AbstractRule rule) { if (!rule.isRegex()) { return true; } String resourceName = rule.getResource(); try { Pattern.compile(resourceName); return true; } catch (Exception e) { return false; } } private List matcherFromRegexRules(String resource) { List compilers = new ArrayList<>(); for (Map.Entry> entry : regexRules.entrySet()) { if (entry.getKey().matcher(resource).matches()) { compilers.addAll(generator.apply(entry.getValue())); } } return compilers; } private synchronized void setRules(Map> regexRules, Map> simpleRules) { this.regexRules = regexRules; this.simpleRules = simpleRules; if (regexRules.isEmpty()) { this.regexCacheRules = Collections.emptyMap(); return; } // rebuild from regex cache rules Map> rebuildCacheRule = new HashMap<>(regexCacheRules.size()); for (String resource : regexCacheRules.keySet()) { rebuildCacheRule.put(resource, matcherFromRegexRules(resource)); } this.regexCacheRules = rebuildCacheRule; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block; /** * A {@link RuntimeException} marks sentinel RPC exception. The stack trace * is removed for high performance. * * @author leyou */ public class SentinelRpcException extends RuntimeException { public SentinelRpcException(String msg) { super(msg); } public SentinelRpcException(Throwable e) { super(e); } @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.authority; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Block exception for request origin access (authority) control. * * @author youji.zj * @author Eric Zhao */ public class AuthorityException extends BlockException { public AuthorityException(String ruleLimitApp) { super(ruleLimitApp); } public AuthorityException(String ruleLimitApp, AuthorityRule rule) { super(ruleLimitApp, rule); } public AuthorityException(String message, Throwable cause) { super(message, cause); } public AuthorityException(String ruleLimitApp, String message) { super(ruleLimitApp, message); } @Override public Throwable fillInStackTrace() { return this; } /** * Get triggered rule. * Note: the rule result is a reference to rule map and SHOULD NOT be modified. * * @return triggered rule * @since 1.4.2 */ @Override public AuthorityRule getRule() { return rule.as(AuthorityRule.class); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.authority; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** * Authority rule is designed for limiting by request origins. * * @author youji.zj */ public class AuthorityRule extends AbstractRule { /** * Mode: 0 for whitelist; 1 for blacklist. */ private int strategy = RuleConstant.AUTHORITY_WHITE; public int getStrategy() { return strategy; } public AuthorityRule setStrategy(int strategy) { this.strategy = strategy; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof AuthorityRule)) { return false; } if (!super.equals(o)) { return false; } AuthorityRule rule = (AuthorityRule)o; return strategy == rule.strategy; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + strategy; return result; } @Override public String toString() { return "AuthorityRule{" + "resource=" + getResource() + ", limitApp=" + getLimitApp() + ", strategy=" + strategy + "} "; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.authority; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.StringUtil; /** * Rule checker for white/black list authority. * * @author Eric Zhao * @since 0.2.0 */ final class AuthorityRuleChecker { static boolean passCheck(AuthorityRule rule, Context context) { String requester = context.getOrigin(); // Empty origin or empty limitApp will pass. if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) { return true; } // Do exact match with origin name. int pos = rule.getLimitApp().indexOf(requester); boolean contain = pos > -1; if (contain) { boolean exactlyMatch = false; String[] appArray = rule.getLimitApp().split(","); for (String app : appArray) { if (requester.equals(app)) { exactlyMatch = true; break; } } contain = exactlyMatch; } int strategy = rule.getStrategy(); if (strategy == RuleConstant.AUTHORITY_BLACK && contain) { return false; } if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) { return false; } return true; } private AuthorityRuleChecker() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.authority; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; /** * Manager for authority rules. * * @author youji.zj * @author jialiang.linjl * @author Eric Zhao */ public final class AuthorityRuleManager { private static volatile RuleManager authorityRules = new RuleManager<>(); private static final RulePropertyListener LISTENER = new RulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(LISTENER); } public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { if (currentProperty != null) { currentProperty.removeListener(LISTENER); } property.addListener(LISTENER); currentProperty = property; RecordLog.info("[AuthorityRuleManager] Registering new property to authority rule manager"); } } /** * Load the authority rules to memory. * * @param rules list of authority rules */ public static void loadRules(List rules) { currentProperty.updateValue(rules); } public static boolean hasConfig(String resource) { return authorityRules.hasConfig(resource); } /** * Get a copy of the rules. * * @return a new copy of the rules. */ public static List getRules() { return authorityRules.getRules(); } private static class RulePropertyListener implements PropertyListener> { @Override public synchronized void configLoad(List value) { authorityRules.updateRules(loadAuthorityConf(value)); RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules); } @Override public synchronized void configUpdate(List conf) { authorityRules.updateRules(loadAuthorityConf(conf)); RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules); } private Map> loadAuthorityConf(List list) { Map> newRuleMap = new ConcurrentHashMap<>(); if (list == null || list.isEmpty()) { return newRuleMap; } for (AuthorityRule rule : list) { if (!isValidRule(rule)) { RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } String identity = rule.getResource(); List ruleSet = newRuleMap.get(identity); // putIfAbsent if (ruleSet == null) { ruleSet = new ArrayList<>(); ruleSet.add(rule); newRuleMap.put(identity, ruleSet); } else { // One resource should only have at most one authority rule, so just ignore redundant rules. RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString()); } } return newRuleMap; } } static List getRules(String resource) { return authorityRules.getRules(resource); } public static boolean isValidRule(AuthorityRule rule) { return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp()) && RuleManager.checkRegexResourceField(rule); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.authority; import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.spi.Spi; /** * A {@link ProcessorSlot} that dedicates to {@link AuthorityRule} checking. * * @author leyou * @author Eric Zhao */ @Spi(order = Constants.ORDER_AUTHORITY_SLOT) public class AuthoritySlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkBlackWhiteAuthority(resourceWrapper, context); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException { List rules = AuthorityRuleManager.getRules(resource.getName()); if (rules == null) { return; } for (AuthorityRule rule : rules) { if (!AuthorityRuleChecker.passCheck(rule, context)) { throw new AuthorityException(context.getOrigin(), rule); } } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DefaultCircuitBreakerRuleManager.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * The rule manager for universal default circuit breaker rule. * * @author wuwen * @author Eric Zhao * @since 2.0.0 */ public final class DefaultCircuitBreakerRuleManager { public static final String DEFAULT_KEY = "*"; private static volatile Map> circuitBreakers = new ConcurrentHashMap<>(); private static volatile Set rules = new HashSet<>(); /** * Resources in this set will not be affected by default rules. */ private static final Set excludedResource = ConcurrentHashMap.newKeySet(); private static final DefaultCircuitBreakerRuleManager.RulePropertyListener LISTENER = new DefaultCircuitBreakerRuleManager.RulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(LISTENER); } /** * Listen to the {@link SentinelProperty} for default circuit breaker rules. * * @param property the property to listen. */ public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { RecordLog.info("Registering new property to DefaultCircuitBreakerRuleManager"); currentProperty.removeListener(LISTENER); property.addListener(LISTENER); currentProperty = property; } } static List getDefaultCircuitBreakers(String resourceName) { if (rules == null || rules.isEmpty()) { return null; } List circuitBreakers = DefaultCircuitBreakerRuleManager.circuitBreakers.get(resourceName); if (circuitBreakers == null && !rules.isEmpty() && !excludedResource.contains(resourceName)) { circuitBreakers = new ArrayList<>(); for (DegradeRule rule : rules) { circuitBreakers.add(DefaultCircuitBreakerRuleManager.newCircuitBreakerFrom(rule)); } DefaultCircuitBreakerRuleManager.circuitBreakers.put(resourceName, circuitBreakers); return circuitBreakers; } return circuitBreakers; } /** * Exclude the resource that does not require default rules. * * @param resourceName the name of resource that does not require default rules */ public static void addExcludedResource(String resourceName) { if (StringUtil.isEmpty(resourceName)) { return; } excludedResource.add(resourceName); } public static void removeExcludedResource(String resourceName) { if (StringUtil.isEmpty(resourceName)) { return; } excludedResource.remove(resourceName); } public static void clearExcludedResource() { excludedResource.clear(); } /** * Load default circuit breaker rules, former rules will be replaced. * * @param rules new rules to load. */ public static boolean loadRules(List rules) { try { return currentProperty.updateValue(rules); } catch (Throwable e) { RecordLog.error("[DefaultCircuitBreakerRuleManager] Unexpected error when loading default rules", e); return false; } } public static boolean isValidDefaultRule(DegradeRule rule) { if (!DegradeRuleManager.isValidRule(rule)) { return false; } return rule.getResource().equals(DEFAULT_KEY); } /** * Create a circuit breaker instance from provided circuit breaking rule. * * @param rule a valid circuit breaking rule * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type */ private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) { switch (rule.getGrade()) { case RuleConstant.DEGRADE_GRADE_RT: return new ResponseTimeCircuitBreaker(rule); case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: return new ExceptionCircuitBreaker(rule); default: return null; } } private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) { List cbs = getCircuitBreakers(rule.getResource()); if (cbs == null || cbs.isEmpty()) { return newCircuitBreakerFrom(rule); } for (CircuitBreaker cb : cbs) { if (rule.equals(cb.getRule())) { // Reuse the circuit breaker if the rule remains unchanged. return cb; } } return newCircuitBreakerFrom(rule); } static List getCircuitBreakers(String resourceName) { return circuitBreakers.get(resourceName); } private static class RulePropertyListener implements PropertyListener> { private synchronized void reloadFrom(List list) { if (list == null || list.isEmpty()) { // clearing all rules DefaultCircuitBreakerRuleManager.circuitBreakers = new ConcurrentHashMap<>(); DefaultCircuitBreakerRuleManager.rules = new HashSet<>(); return; } Set rules = new HashSet(); for (DegradeRule rule : list) { if (!isValidDefaultRule(rule)) { RecordLog.warn( "[DefaultCircuitBreakerRuleManager] Ignoring invalid rule when loading new rules: {}", rule); } else { if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } // TODO: Set a special ID for default circuit breaker rule (so that it could be identified) rules.add(rule); } } Map> cbMap = new ConcurrentHashMap>(8); for (String resourceNameKey : DefaultCircuitBreakerRuleManager.circuitBreakers.keySet()) { List cbs = new ArrayList(); for (DegradeRule rule : rules) { CircuitBreaker cb = getExistingSameCbOrNew(rule); cbs.add(cb); } cbMap.put(resourceNameKey, cbs); } DefaultCircuitBreakerRuleManager.rules = rules; DefaultCircuitBreakerRuleManager.circuitBreakers = cbMap; } @Override public void configUpdate(List conf) { reloadFrom(conf); RecordLog.info("[DefaultCircuitBreakerRuleManager] Default circuit breaker rules has been updated to: {}", rules); } @Override public void configLoad(List conf) { reloadFrom(conf); RecordLog.info("[DefaultCircuitBreakerRuleManager] Default circuit breaker rules loaded: {}", rules); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DefaultCircuitBreakerSlot.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.spi.Spi; import java.util.List; /** *

A {@link ProcessorSlot} dedicates to universal default circuit breaker.

* * @author wuwen * @since 2.0.0 */ @Spi(order = Constants.ORDER_DEFAULT_CIRCUIT_BREAKER_SLOT) public class DefaultCircuitBreakerSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { performChecking(context, resourceWrapper); fireEntry(context, resourceWrapper, node, count, prioritized, args); } private void performChecking(Context context, ResourceWrapper r) throws BlockException { // If user has set a degrade rule for the resource, the default rule will not be activated if (DegradeRuleManager.hasConfig(r.getName())) { return; } List circuitBreakers = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(r.getName()); if (circuitBreakers == null || circuitBreakers.isEmpty()) { return; } for (CircuitBreaker cb : circuitBreakers) { if (!cb.tryPass(context)) { throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule()); } } } @Override public void exit(Context context, ResourceWrapper r, int count, Object... args) { Entry curEntry = context.getCurEntry(); if (curEntry.getBlockError() != null) { fireExit(context, r, count, args); return; } if (DegradeRuleManager.hasConfig(r.getName())) { fireExit(context, r, count, args); return; } List circuitBreakers = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(r.getName()); if (circuitBreakers == null || circuitBreakers.isEmpty()) { fireExit(context, r, count, args); return; } if (curEntry.getBlockError() == null) { // passed request for (CircuitBreaker circuitBreaker : circuitBreakers) { circuitBreaker.onRequestComplete(context); } } fireExit(context, r, count, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.slots.block.BlockException; /*** * @author youji.zj */ public class DegradeException extends BlockException { public DegradeException(String ruleLimitApp) { super(ruleLimitApp); } public DegradeException(String ruleLimitApp, DegradeRule rule) { super(ruleLimitApp, rule); } public DegradeException(String message, Throwable cause) { super(message, cause); } public DegradeException(String ruleLimitApp, String message) { super(ruleLimitApp, message); } @Override public Throwable fillInStackTrace() { return this; } /** * Get triggered rule. * Note: the rule result is a reference to rule map and SHOULD NOT be modified. * * @return triggered rule * @since 1.4.2 */ @Override public DegradeRule getRule() { return rule.as(DegradeRule.class); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import java.util.Objects; /** *

* Degrade is used when the resources are in an unstable state, these resources * will be degraded within the next defined time window. There are two ways to * measure whether a resource is stable or not: *

*
    *
  • * Average response time ({@code DEGRADE_GRADE_RT}): When * the average RT exceeds the threshold ('count' in 'DegradeRule', in milliseconds), the * resource enters a quasi-degraded state. If the RT of next coming 5 * requests still exceed this threshold, this resource will be downgraded, which * means that in the next time window (defined in 'timeWindow', in seconds) all the * access to this resource will be blocked. *
  • *
  • * Exception ratio: When the ratio of exception count per second and the * success qps exceeds the threshold, access to the resource will be blocked in * the coming window. *
  • *
* * @author jialiang.linjl * @author Eric Zhao */ public class DegradeRule extends AbstractRule { public DegradeRule() {} public DegradeRule(String resourceName) { setResource(resourceName); } /** * Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count). */ private int grade = RuleConstant.DEGRADE_GRADE_RT; /** * Threshold count. The exact meaning depends on the field of grade. *
    *
  • In average RT mode, it means the maximum response time(RT) in milliseconds.
  • *
  • In exception ratio mode, it means exception ratio which between 0.0 and 1.0.
  • *
  • In exception count mode, it means exception count
  • *
      */ private double count; /** * Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will * transform to half-open state for trying a few requests. */ private int timeWindow; /** * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking. * * @since 1.7.0 */ private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT; /** * The threshold of slow request ratio in RT mode. * * @since 1.8.0 */ private double slowRatioThreshold = 1.0d; /** * The interval statistics duration in millisecond. * * @since 1.8.0 */ private int statIntervalMs = 1000; public int getGrade() { return grade; } public DegradeRule setGrade(int grade) { this.grade = grade; return this; } public double getCount() { return count; } public DegradeRule setCount(double count) { this.count = count; return this; } public int getTimeWindow() { return timeWindow; } public DegradeRule setTimeWindow(int timeWindow) { this.timeWindow = timeWindow; return this; } public int getMinRequestAmount() { return minRequestAmount; } public DegradeRule setMinRequestAmount(int minRequestAmount) { this.minRequestAmount = minRequestAmount; return this; } public double getSlowRatioThreshold() { return slowRatioThreshold; } public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) { this.slowRatioThreshold = slowRatioThreshold; return this; } public int getStatIntervalMs() { return statIntervalMs; } public DegradeRule setStatIntervalMs(int statIntervalMs) { this.statIntervalMs = statIntervalMs; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } DegradeRule rule = (DegradeRule)o; return Double.compare(rule.count, count) == 0 && timeWindow == rule.timeWindow && grade == rule.grade && minRequestAmount == rule.minRequestAmount && Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 && statIntervalMs == rule.statIntervalMs; } @Override public int hashCode() { return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount, slowRatioThreshold, statIntervalMs); } @Override public String toString() { return "DegradeRule{" + "resource=" + getResource() + ", grade=" + grade + ", count=" + count + ", limitApp=" + getLimitApp() + ", timeWindow=" + timeWindow + ", minRequestAmount=" + minRequestAmount + ", slowRatioThreshold=" + slowRatioThreshold + ", statIntervalMs=" + statIntervalMs + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** * The rule manager for circuit breaking rules ({@link DegradeRule}). * * @author youji.zj * @author jialiang.linjl * @author Eric Zhao */ public final class DegradeRuleManager { private static volatile RuleManager circuitBreakers = new RuleManager<>(DegradeRuleManager::generateCbs, cb -> cb.getRule().isRegex()); private static volatile RuleManager ruleMap = new RuleManager<>(); private static final RulePropertyListener LISTENER = new RulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(LISTENER); } /** * Listen to the {@link SentinelProperty} for {@link DegradeRule}s. The property is the source * of {@link DegradeRule}s. Degrade rules can also be set by {@link #loadRules(List)} directly. * * @param property the property to listen. */ public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { RecordLog.info("[DegradeRuleManager] Registering new property to degrade rule manager"); currentProperty.removeListener(LISTENER); property.addListener(LISTENER); currentProperty = property; } } static List getCircuitBreakers(String resourceName) { return circuitBreakers.getRules(resourceName); } public static boolean hasConfig(String resource) { return circuitBreakers.hasConfig(resource); } /** *

      Get existing circuit breaking rules.

      *

      Note: DO NOT modify the rules from the returned list directly. * The behavior is undefined.

      * * @return list of existing circuit breaking rules, or empty list if no rules were loaded */ public static List getRules() { return ruleMap.getRules(); } public static Set getRulesOfResource(String resource) { AssertUtil.assertNotBlank(resource, "resource name cannot be blank"); return new HashSet<>(ruleMap.getRules(resource)); } /** * Load {@link DegradeRule}s, former rules will be replaced. * * @param rules new rules to load. */ public static void loadRules(List rules) { try { currentProperty.updateValue(rules); } catch (Throwable e) { RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", e); } } /** * Set degrade rules for provided resource. Former rules of the resource will be replaced. * * @param resourceName valid resource name * @param rules new rule set to load * @return whether the rules has actually been updated * @since 1.5.0 */ public static boolean setRulesForResource(String resourceName, Set rules) { AssertUtil.notEmpty(resourceName, "resourceName cannot be empty"); try { Map> newRuleMap = ruleMap.getOriginalRules(); if (rules == null) { newRuleMap.remove(resourceName); } else { Set newSet = new HashSet<>(); for (DegradeRule rule : rules) { if (isValidRule(rule) && resourceName.equals(rule.getResource())) { newSet.add(rule); } } newRuleMap.put(resourceName, new ArrayList<>(newSet)); } List allRules = new ArrayList<>(); for (List set : newRuleMap.values()) { allRules.addAll(set); } return currentProperty.updateValue(allRules); } catch (Throwable e) { RecordLog.error("[DegradeRuleManager] Unexpected error when setting circuit breaking" + " rules for resource: " + resourceName, e); return false; } } private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) { List cbs = getCircuitBreakers(rule.getResource()); if (cbs == null || cbs.isEmpty()) { return newCircuitBreakerFrom(rule); } for (CircuitBreaker cb : cbs) { if (rule.equals(cb.getRule())) { // Reuse the circuit breaker if the rule remains unchanged. return cb; } } return newCircuitBreakerFrom(rule); } /** * Create a circuit breaker instance from provided circuit breaking rule. * * @param rule a valid circuit breaking rule * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type */ private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) { switch (rule.getGrade()) { case RuleConstant.DEGRADE_GRADE_RT: return new ResponseTimeCircuitBreaker(rule); case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: return new ExceptionCircuitBreaker(rule); default: return null; } } public static boolean isValidRule(DegradeRule rule) { boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 && rule.getTimeWindow() > 0; if (!baseValid) { return false; } if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) { return false; } if (!RuleManager.checkRegexResourceField(rule)) { return false; } switch (rule.getGrade()) { case RuleConstant.DEGRADE_GRADE_RT: return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1; case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: return rule.getCount() <= 1; case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: return true; default: return false; } } private static List generateCbs(List cbs) { return cbs.stream().map(cb -> newCircuitBreakerFrom(cb.getRule())).collect(Collectors.toList()); } private static class RulePropertyListener implements PropertyListener> { private synchronized void reloadFrom(List list) { Map> cbs = buildCircuitBreakers(list); Map> rules = buildCircuitBreakerRules(cbs); circuitBreakers.updateRules(cbs); ruleMap.updateRules(rules); } @Override public void configUpdate(List conf) { reloadFrom(conf); RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: {}", ruleMap); } @Override public void configLoad(List conf) { reloadFrom(conf); RecordLog.info("[DegradeRuleManager] Degrade rules loaded: {}", ruleMap); } private Map> buildCircuitBreakerRules(Map> cbs) { Map> result = new HashMap<>(cbs.size()); for (Map.Entry> entry : cbs.entrySet()) { String resource = entry.getKey(); Set rules = entry.getValue().stream().map(CircuitBreaker::getRule).collect(Collectors.toSet()); result.put(resource, new ArrayList<>(rules)); } return result; } private Map> buildCircuitBreakers(List list) { Map> cbMap = new HashMap<>(8); if (list == null || list.isEmpty()) { return cbMap; } for (DegradeRule rule : list) { if (!isValidRule(rule)) { RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: {}", rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } CircuitBreaker cb = getExistingSameCbOrNew(rule); if (cb == null) { RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: {}", rule); continue; } String resourceName = rule.getResource(); List cbList = cbMap.get(resourceName); if (cbList == null) { cbList = new ArrayList<>(); cbMap.put(resourceName, cbList); } cbList.add(cb); } return cbMap; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.spi.Spi; /** * A {@link ProcessorSlot} dedicates to circuit breaking. * * @author Carpenter Lee * @author Eric Zhao */ @Spi(order = Constants.ORDER_DEGRADE_SLOT) public class DegradeSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { performChecking(context, resourceWrapper); fireEntry(context, resourceWrapper, node, count, prioritized, args); } void performChecking(Context context, ResourceWrapper r) throws BlockException { List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); if (circuitBreakers == null || circuitBreakers.isEmpty()) { return; } for (CircuitBreaker cb : circuitBreakers) { if (!cb.tryPass(context)) { throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule()); } } } @Override public void exit(Context context, ResourceWrapper r, int count, Object... args) { Entry curEntry = context.getCurEntry(); if (curEntry.getBlockError() != null) { fireExit(context, r, count, args); return; } List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); if (circuitBreakers == null || circuitBreakers.isEmpty()) { fireExit(context, r, count, args); return; } if (curEntry.getBlockError() == null) { // passed request for (CircuitBreaker circuitBreaker : circuitBreakers) { circuitBreaker.onRequestComplete(context); } } fireExit(context, r, count, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import java.util.concurrent.atomic.AtomicReference; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.util.function.BiConsumer; /** * @author Eric Zhao * @since 1.8.0 */ public abstract class AbstractCircuitBreaker implements CircuitBreaker { protected static final double MAX_RATIO = 1.0d; protected final DegradeRule rule; protected final int recoveryTimeoutMs; private final EventObserverRegistry observerRegistry; protected final AtomicReference currentState = new AtomicReference<>(State.CLOSED); protected volatile long nextRetryTimestamp; public AbstractCircuitBreaker(DegradeRule rule) { this(rule, EventObserverRegistry.getInstance()); } AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) { AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null"); if (!DegradeRuleManager.isValidRule(rule)) { throw new IllegalArgumentException("Invalid DegradeRule: " + rule); } this.observerRegistry = observerRegistry; this.rule = rule; this.recoveryTimeoutMs = rule.getTimeWindow() * 1000; } @Override public DegradeRule getRule() { return rule; } @Override public State currentState() { return currentState.get(); } @Override public boolean tryPass(Context context) { // Template implementation. if (currentState.get() == State.CLOSED) { return true; } if (currentState.get() == State.OPEN) { // For half-open state we allow a request for probing. return retryTimeoutArrived() && fromOpenToHalfOpen(context); } return false; } /** * Reset the statistic data. */ abstract void resetStat(); protected boolean retryTimeoutArrived() { return TimeUtil.currentTimeMillis() >= nextRetryTimestamp; } protected void updateNextRetryTimestamp() { this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs; } protected boolean fromCloseToOpen(double snapshotValue) { State prev = State.CLOSED; if (currentState.compareAndSet(prev, State.OPEN)) { updateNextRetryTimestamp(); notifyObservers(prev, State.OPEN, snapshotValue); return true; } return false; } protected boolean fromOpenToHalfOpen(Context context) { if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) { notifyObservers(State.OPEN, State.HALF_OPEN, null); Entry entry = context.getCurEntry(); entry.whenTerminate(new BiConsumer() { @Override public void accept(Context context, Entry entry) { // Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638 // Without the hook, the circuit breaker won't recover from half-open state in some circumstances // when the request is actually blocked by upcoming rules (not only degrade rules). if (entry.getBlockError() != null) { // Fallback to OPEN due to detecting request is blocked currentState.compareAndSet(State.HALF_OPEN, State.OPEN); notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d); } } }); return true; } return false; } private void notifyObservers(CircuitBreaker.State prevState, CircuitBreaker.State newState, Double snapshotValue) { for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) { observer.onStateChange(prevState, newState, rule, snapshotValue); } } protected boolean fromHalfOpenToOpen(double snapshotValue) { if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) { updateNextRetryTimestamp(); notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue); return true; } return false; } protected boolean fromHalfOpenToClose() { if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) { resetStat(); notifyObservers(State.HALF_OPEN, State.CLOSED, null); return true; } return false; } protected void transformToOpen(double triggerValue) { State cs = currentState.get(); switch (cs) { case CLOSED: fromCloseToOpen(triggerValue); break; case HALF_OPEN: fromHalfOpenToOpen(triggerValue); break; default: break; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; /** *

      Basic circuit breaker interface.

      * * @author Eric Zhao */ public interface CircuitBreaker { /** * Get the associated circuit breaking rule. * * @return associated circuit breaking rule */ DegradeRule getRule(); /** * Acquires permission of an invocation only if it is available at the time of invoking. * * @param context context of current invocation * @return {@code true} if permission was acquired and {@code false} otherwise */ boolean tryPass(Context context); /** * Get current state of the circuit breaker. * * @return current state of the circuit breaker */ State currentState(); /** *

      Record a completed request with the context and handle state transformation of the circuit breaker.

      *

      Called when a passed invocation finished.

      * * @param context context of current invocation */ void onRequestComplete(Context context); /** * Circuit breaker state. */ enum State { /** * In {@code OPEN} state, all requests will be rejected until the next recovery time point. */ OPEN, /** * In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation. * If the invocation is abnormal according to the strategy (e.g. it's slow), the circuit breaker * will re-transform to the {@code OPEN} state and wait for the next recovery time point; * otherwise the resource will be regarded as "recovered" and the circuit breaker * will cease cutting off requests and transform to {@code CLOSED} state. */ HALF_OPEN, /** * In {@code CLOSED} state, all requests are permitted. When current metric value exceeds the threshold, * the circuit breaker will transform to {@code OPEN} state. */ CLOSED } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; /** * @author Eric Zhao * @since 1.8.0 */ public interface CircuitBreakerStateChangeObserver { /** *

      Observer method triggered when circuit breaker state changed. The transformation could be:

      *
        *
      • From {@code CLOSED} to {@code OPEN} (with the triggered metric)
      • *
      • From {@code OPEN} to {@code HALF_OPEN}
      • *
      • From {@code OPEN} to {@code CLOSED}
      • *
      • From {@code HALF_OPEN} to {@code OPEN} (with the triggered metric)
      • *
      * * @param prevState previous state of the circuit breaker * @param newState new state of the circuit breaker * @param rule associated rule * @param snapshotValue triggered value on circuit breaker opens (null if the new state is CLOSED or HALF_OPEN) */ void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule, Double snapshotValue); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; /** * @author Eric Zhao * @since 1.8.0 */ public enum CircuitBreakerStrategy { /** * Circuit breaker opens (cuts off) when slow request ratio exceeds the threshold. */ SLOW_REQUEST_RATIO(0), /** * Circuit breaker opens (cuts off) when error ratio exceeds the threshold. */ ERROR_RATIO(1), /** * Circuit breaker opens (cuts off) when error count exceeds the threshold. */ ERROR_COUNT(2); private int type; CircuitBreakerStrategy(int type) { this.type = type; } public int getType() { return type; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.csp.sentinel.util.AssertUtil; /** *

      Registry for circuit breaker event observers.

      * * @author Eric Zhao * @since 1.8.0 */ public class EventObserverRegistry { private final Map stateChangeObserverMap = new HashMap<>(); /** * Register a circuit breaker state change observer. * * @param name observer name * @param observer a valid observer */ public void addStateChangeObserver(String name, CircuitBreakerStateChangeObserver observer) { AssertUtil.notNull(name, "name cannot be null"); AssertUtil.notNull(observer, "observer cannot be null"); stateChangeObserverMap.put(name, observer); } public boolean removeStateChangeObserver(String name) { AssertUtil.notNull(name, "name cannot be null"); return stateChangeObserverMap.remove(name) != null; } /** * Get all registered state chane observers. * * @return all registered state chane observers */ public List getStateChangeObservers() { return new ArrayList<>(stateChangeObserverMap.values()); } public static EventObserverRegistry getInstance() { return InstanceHolder.instance; } private static class InstanceHolder { private static EventObserverRegistry instance = new EventObserverRegistry(); } EventObserverRegistry() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import java.util.List; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.util.AssertUtil; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; /** * @author Eric Zhao * @since 1.8.0 */ public class ExceptionCircuitBreaker extends AbstractCircuitBreaker { private final int strategy; private final int minRequestAmount; private final double threshold; private final LeapArray stat; public ExceptionCircuitBreaker(DegradeRule rule) { this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs())); } ExceptionCircuitBreaker(DegradeRule rule, LeapArray stat) { super(rule); this.strategy = rule.getGrade(); boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT; AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count"); AssertUtil.notNull(stat, "stat cannot be null"); this.minRequestAmount = rule.getMinRequestAmount(); this.threshold = rule.getCount(); this.stat = stat; } @Override protected void resetStat() { // Reset current bucket (bucket count = 1). stat.currentWindow().value().reset(); } @Override public void onRequestComplete(Context context) { Entry entry = context.getCurEntry(); if (entry == null) { return; } Throwable error = entry.getError(); SimpleErrorCounter counter = stat.currentWindow().value(); if (error != null) { counter.getErrorCount().add(1); } counter.getTotalCount().add(1); handleStateChangeWhenThresholdExceeded(error); } private void handleStateChangeWhenThresholdExceeded(Throwable error) { if (currentState.get() == State.OPEN) { return; } if (currentState.get() == State.HALF_OPEN) { // In detecting request if (error == null) { fromHalfOpenToClose(); } else { fromHalfOpenToOpen(1.0d); } return; } List counters = stat.values(); long errCount = 0; long totalCount = 0; for (SimpleErrorCounter counter : counters) { errCount += counter.errorCount.sum(); totalCount += counter.totalCount.sum(); } if (totalCount < minRequestAmount) { return; } double curCount = errCount; if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) { // Use errorRatio curCount = errCount * 1.0d / totalCount; // special case when ratio equals 1.0 if (Double.compare(curCount, threshold) == 0 && Double.compare(threshold, MAX_RATIO) == 0) { transformToOpen(curCount); return; } } if (curCount > threshold) { transformToOpen(curCount); } } static class SimpleErrorCounter { private LongAdder errorCount; private LongAdder totalCount; public SimpleErrorCounter() { this.errorCount = new LongAdder(); this.totalCount = new LongAdder(); } public LongAdder getErrorCount() { return errorCount; } public LongAdder getTotalCount() { return totalCount; } public SimpleErrorCounter reset() { errorCount.reset(); totalCount.reset(); return this; } @Override public String toString() { return "SimpleErrorCounter{" + "errorCount=" + errorCount + ", totalCount=" + totalCount + '}'; } } static class SimpleErrorCounterLeapArray extends LeapArray { public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); } @Override public SimpleErrorCounter newEmptyBucket(long timeMillis) { return new SimpleErrorCounter(); } @Override protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { // Update the start time and reset value. w.resetTo(startTime); w.value().reset(); return w; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import java.util.List; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author Eric Zhao * @since 1.8.0 */ public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker { private final long maxAllowedRt; private final double maxSlowRequestRatio; private final int minRequestAmount; private final LeapArray slidingCounter; public ResponseTimeCircuitBreaker(DegradeRule rule) { this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs())); } ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray stat) { super(rule); AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT"); AssertUtil.notNull(stat, "stat cannot be null"); this.maxAllowedRt = Math.round(rule.getCount()); this.maxSlowRequestRatio = rule.getSlowRatioThreshold(); this.minRequestAmount = rule.getMinRequestAmount(); this.slidingCounter = stat; } @Override public void resetStat() { // Reset current bucket (bucket count = 1). slidingCounter.currentWindow().value().reset(); } @Override public void onRequestComplete(Context context) { SlowRequestCounter counter = slidingCounter.currentWindow().value(); Entry entry = context.getCurEntry(); if (entry == null) { return; } long completeTime = entry.getCompleteTimestamp(); if (completeTime <= 0) { completeTime = TimeUtil.currentTimeMillis(); } long rt = completeTime - entry.getCreateTimestamp(); if (rt > maxAllowedRt) { counter.slowCount.add(1); } counter.totalCount.add(1); handleStateChangeWhenThresholdExceeded(rt); } private void handleStateChangeWhenThresholdExceeded(long rt) { if (currentState.get() == State.OPEN) { return; } if (currentState.get() == State.HALF_OPEN) { // In detecting request // TODO: improve logic for half-open recovery if (rt > maxAllowedRt) { fromHalfOpenToOpen(1.0d); } else { fromHalfOpenToClose(); } return; } List counters = slidingCounter.values(); long slowCount = 0; long totalCount = 0; for (SlowRequestCounter counter : counters) { slowCount += counter.slowCount.sum(); totalCount += counter.totalCount.sum(); } if (totalCount < minRequestAmount) { return; } double currentRatio = slowCount * 1.0d / totalCount; if (currentRatio > maxSlowRequestRatio) { transformToOpen(currentRatio); } if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 && Double.compare(maxSlowRequestRatio, MAX_RATIO) == 0) { transformToOpen(currentRatio); } } static class SlowRequestCounter { private LongAdder slowCount; private LongAdder totalCount; public SlowRequestCounter() { this.slowCount = new LongAdder(); this.totalCount = new LongAdder(); } public LongAdder getSlowCount() { return slowCount; } public LongAdder getTotalCount() { return totalCount; } public SlowRequestCounter reset() { slowCount.reset(); totalCount.reset(); return this; } @Override public String toString() { return "SlowRequestCounter{" + "slowCount=" + slowCount + ", totalCount=" + totalCount + '}'; } } static class SlowRequestLeapArray extends LeapArray { public SlowRequestLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); } @Override public SlowRequestCounter newEmptyBucket(long timeMillis) { return new SlowRequestCounter(); } @Override protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { w.resetTo(startTime); w.value().reset(); return w; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import java.util.Objects; /** * Flow rule config in cluster mode. * * @author Eric Zhao * @since 1.4.0 */ public class ClusterFlowConfig { /** * Global unique ID. */ private Long flowId; /** * Threshold type (average by local value or global value). */ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; private boolean fallbackToLocalWhenFail = true; /** * 0: normal. */ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT; /** * The time interval length of the statistic sliding window (in milliseconds) */ private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; /** * if the client keep the token for more than resourceTimeout,resourceTimeoutStrategy will work. */ private long resourceTimeout = 2000; /** * 0:ignore,1:release the token. */ private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY; /** * if the request(prioritized=true) is block,acquireRefuseStrategy will work.. * 0:ignore and block. * 1:try again . * 2:try until success. */ private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY; /** * if a client is offline,the server will delete all the token the client holds after clientOfflineTime. */ private long clientOfflineTime = 2000; public long getResourceTimeout() { return resourceTimeout; } public void setResourceTimeout(long resourceTimeout) { this.resourceTimeout = resourceTimeout; } public int getResourceTimeoutStrategy() { return resourceTimeoutStrategy; } public void setResourceTimeoutStrategy(int resourceTimeoutStrategy) { this.resourceTimeoutStrategy = resourceTimeoutStrategy; } public int getAcquireRefuseStrategy() { return acquireRefuseStrategy; } public void setAcquireRefuseStrategy(int acquireRefuseStrategy) { this.acquireRefuseStrategy = acquireRefuseStrategy; } public long getClientOfflineTime() { return clientOfflineTime; } public void setClientOfflineTime(long clientOfflineTime) { this.clientOfflineTime = clientOfflineTime; } public Long getFlowId() { return flowId; } public ClusterFlowConfig setFlowId(Long flowId) { this.flowId = flowId; return this; } public int getThresholdType() { return thresholdType; } public ClusterFlowConfig setThresholdType(int thresholdType) { this.thresholdType = thresholdType; return this; } public int getStrategy() { return strategy; } public ClusterFlowConfig setStrategy(int strategy) { this.strategy = strategy; return this; } public boolean isFallbackToLocalWhenFail() { return fallbackToLocalWhenFail; } public ClusterFlowConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) { this.fallbackToLocalWhenFail = fallbackToLocalWhenFail; return this; } public int getSampleCount() { return sampleCount; } public ClusterFlowConfig setSampleCount(int sampleCount) { this.sampleCount = sampleCount; return this; } public int getWindowIntervalMs() { return windowIntervalMs; } public ClusterFlowConfig setWindowIntervalMs(int windowIntervalMs) { this.windowIntervalMs = windowIntervalMs; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClusterFlowConfig that = (ClusterFlowConfig) o; if (thresholdType != that.thresholdType) { return false; } if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } if (strategy != that.strategy) { return false; } if (sampleCount != that.sampleCount) { return false; } if (windowIntervalMs != that.windowIntervalMs) { return false; } if (resourceTimeout != that.resourceTimeout) { return false; } if (clientOfflineTime != that.clientOfflineTime) { return false; } if (resourceTimeoutStrategy != that.resourceTimeoutStrategy) { return false; } if (acquireRefuseStrategy != that.acquireRefuseStrategy) { return false; } return Objects.equals(flowId, that.flowId); } @Override public int hashCode() { int result = flowId != null ? flowId.hashCode() : 0; result = 31 * result + thresholdType; result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); result = 31 * result + strategy; result = 31 * result + sampleCount; result = 31 * result + windowIntervalMs; result = (int) (31 * result + resourceTimeout); result = (int) (31 * result + clientOfflineTime); result = 31 * result + resourceTimeoutStrategy; result = 31 * result + acquireRefuseStrategy; return result; } @Override public String toString() { return "ClusterFlowConfig{" + "flowId=" + flowId + ", thresholdType=" + thresholdType + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + ", strategy=" + strategy + ", sampleCount=" + sampleCount + ", windowIntervalMs=" + windowIntervalMs + ", resourceTimeout=" + resourceTimeout + ", resourceTimeoutStrategy=" + resourceTimeoutStrategy + ", acquireRefuseStrategy=" + acquireRefuseStrategy + ", clientOfflineTime=" + clientOfflineTime + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.config.SentinelConfig; /** * @author jialiang.linjl */ class ColdFactorProperty { public static volatile int coldFactor = SentinelConfig.coldFactor(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.slots.block.BlockException; /*** * @author youji.zj */ public class FlowException extends BlockException { public FlowException(String ruleLimitApp) { super(ruleLimitApp); } public FlowException(String ruleLimitApp, FlowRule rule) { super(ruleLimitApp, rule); } public FlowException(String message, Throwable cause) { super(message, cause); } public FlowException(String ruleLimitApp, String message) { super(ruleLimitApp, message); } @Override public Throwable fillInStackTrace() { return this; } /** * Get triggered rule. * Note: the rule result is a reference to rule map and SHOULD NOT be modified. * * @return triggered rule * @since 1.4.2 */ @Override public FlowRule getRule() { return rule.as(FlowRule.class); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** *

      * Each flow rule is mainly composed of three factors: grade, * strategy and controlBehavior: *

      *
        *
      • The {@link #grade} represents the threshold type of flow control (by QPS or thread count).
      • *
      • The {@link #strategy} represents the strategy based on invocation relation.
      • *
      • The {@link #controlBehavior} represents the QPS shaping behavior (actions on incoming request when QPS * exceeds the threshold).
      • *
      * * @author jialiang.linjl * @author Eric Zhao */ public class FlowRule extends AbstractRule { public FlowRule() { super(); setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } public FlowRule(String resourceName) { super(); setResource(resourceName); setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } /** * The threshold type of flow control (0: thread count, 1: QPS). */ private int grade = RuleConstant.FLOW_GRADE_QPS; /** * Flow control threshold count. */ private double count; /** * Flow control strategy based on invocation chain. * * {@link RuleConstant#STRATEGY_DIRECT} for direct flow control (by origin); * {@link RuleConstant#STRATEGY_RELATE} for relevant flow control (with relevant resource); * {@link RuleConstant#STRATEGY_CHAIN} for chain flow control (by entrance resource). */ private int strategy = RuleConstant.STRATEGY_DIRECT; /** * Reference resource in flow control with relevant resource or context. */ private String refResource; /** * Rate limiter control behavior. * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter */ private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT; private int warmUpPeriodSec = 10; /** * Max queueing time in rate limiter behavior. */ private int maxQueueingTimeMs = 500; private boolean clusterMode; /** * Flow rule config for cluster mode. */ private ClusterFlowConfig clusterConfig; /** * The traffic shaping (throttling) controller. */ private TrafficShapingController controller; public int getControlBehavior() { return controlBehavior; } public FlowRule setControlBehavior(int controlBehavior) { this.controlBehavior = controlBehavior; return this; } public int getMaxQueueingTimeMs() { return maxQueueingTimeMs; } public FlowRule setMaxQueueingTimeMs(int maxQueueingTimeMs) { this.maxQueueingTimeMs = maxQueueingTimeMs; return this; } FlowRule setRater(TrafficShapingController rater) { this.controller = rater; return this; } TrafficShapingController getRater() { return controller; } public int getWarmUpPeriodSec() { return warmUpPeriodSec; } public FlowRule setWarmUpPeriodSec(int warmUpPeriodSec) { this.warmUpPeriodSec = warmUpPeriodSec; return this; } public int getGrade() { return grade; } public FlowRule setGrade(int grade) { this.grade = grade; return this; } public double getCount() { return count; } public FlowRule setCount(double count) { this.count = count; return this; } public int getStrategy() { return strategy; } public FlowRule setStrategy(int strategy) { this.strategy = strategy; return this; } public String getRefResource() { return refResource; } public FlowRule setRefResource(String refResource) { this.refResource = refResource; return this; } public boolean isClusterMode() { return clusterMode; } public FlowRule setClusterMode(boolean clusterMode) { this.clusterMode = clusterMode; return this; } public ClusterFlowConfig getClusterConfig() { return clusterConfig; } public FlowRule setClusterConfig(ClusterFlowConfig clusterConfig) { this.clusterConfig = clusterConfig; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } FlowRule rule = (FlowRule)o; if (grade != rule.grade) { return false; } if (Double.compare(rule.count, count) != 0) { return false; } if (strategy != rule.strategy) { return false; } if (controlBehavior != rule.controlBehavior) { return false; } if (warmUpPeriodSec != rule.warmUpPeriodSec) { return false; } if (maxQueueingTimeMs != rule.maxQueueingTimeMs) { return false; } if (clusterMode != rule.clusterMode) { return false; } if (refResource != null ? !refResource.equals(rule.refResource) : rule.refResource != null) { return false; } return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null; } @Override public int hashCode() { int result = super.hashCode(); long temp; result = 31 * result + grade; temp = Double.doubleToLongBits(count); result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + strategy; result = 31 * result + (refResource != null ? refResource.hashCode() : 0); result = 31 * result + controlBehavior; result = 31 * result + warmUpPeriodSec; result = 31 * result + maxQueueingTimeMs; result = 31 * result + (clusterMode ? 1 : 0); result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0); return result; } @Override public String toString() { return "FlowRule{" + "resource=" + getResource() + ", limitApp=" + getLimitApp() + ", grade=" + grade + ", count=" + count + ", strategy=" + strategy + ", refResource=" + refResource + ", controlBehavior=" + controlBehavior + ", warmUpPeriodSec=" + warmUpPeriodSec + ", maxQueueingTimeMs=" + maxQueueingTimeMs + ", clusterMode=" + clusterMode + ", clusterConfig=" + clusterConfig + ", controller=" + controller + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.Collection; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Function; /** * Rule checker for flow control rules. * * @author Eric Zhao */ public class FlowRuleChecker { public void checkFlow(Function> ruleProvider, ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException { if (ruleProvider == null || resource == null) { return; } Collection rules = ruleProvider.apply(resource.getName()); if (rules != null) { for (FlowRule rule : rules) { if (!canPassCheck(rule, context, node, count, prioritized)) { throw new FlowException(rule.getLimitApp(), rule); } } } } public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount) { return canPassCheck(rule, context, node, acquireCount, false); } public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { String limitApp = rule.getLimitApp(); if (limitApp == null) { return true; } if (rule.isClusterMode()) { return passClusterCheck(rule, context, node, acquireCount, prioritized); } return passLocalCheck(rule, context, node, acquireCount, prioritized); } private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); if (selectedNode == null) { return true; } return rule.getRater().canPass(selectedNode, acquireCount, prioritized); } static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { String refResource = rule.getRefResource(); int strategy = rule.getStrategy(); if (StringUtil.isEmpty(refResource)) { return null; } if (strategy == RuleConstant.STRATEGY_RELATE) { return ClusterBuilderSlot.getClusterNode(refResource); } if (strategy == RuleConstant.STRATEGY_CHAIN) { if (!refResource.equals(context.getName())) { return null; } return node; } // No node. return null; } private static boolean filterOrigin(String origin) { // Origin cannot be `default` or `other`. return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin); } static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) { // The limit app should not be empty. String limitApp = rule.getLimitApp(); int strategy = rule.getStrategy(); String origin = context.getOrigin(); if (limitApp.equals(origin) && filterOrigin(origin)) { if (strategy == RuleConstant.STRATEGY_DIRECT) { // Matches limit origin, return origin statistic node. return context.getOriginNode(); } return selectReferenceNode(rule, context, node); } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { if (strategy == RuleConstant.STRATEGY_DIRECT) { // Return the cluster node. return node.getClusterNode(); } return selectReferenceNode(rule, context, node); } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp) && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) { if (strategy == RuleConstant.STRATEGY_DIRECT) { return context.getOriginNode(); } return selectReferenceNode(rule, context, node); } return null; } private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { try { TokenService clusterService = pickClusterService(); if (clusterService == null) { return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); } long flowId = rule.getClusterConfig().getFlowId(); TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized); return applyTokenResult(result, rule, context, node, acquireCount, prioritized); // If client is absent, then fallback to local mode. } catch (Throwable ex) { RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); } // Fallback to local flow control when token client or server for this rule is not available. // If fallback is not enabled, then directly pass. return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); } private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { return passLocalCheck(rule, context, node, acquireCount, prioritized); } else { // The rule won't be activated, just pass. return true; } } private static TokenService pickClusterService() { if (ClusterStateManager.isClient()) { return TokenClientProvider.getClient(); } if (ClusterStateManager.isServer()) { return EmbeddedClusterTokenServerProvider.getServer(); } return null; } private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { switch (result.getStatus()) { case TokenResultStatus.OK: return true; case TokenResultStatus.SHOULD_WAIT: // Wait for next tick. try { Thread.sleep(result.getWaitInMs()); } catch (InterruptedException e) { e.printStackTrace(); } return true; case TokenResultStatus.NO_RULE_EXISTS: case TokenResultStatus.BAD_REQUEST: case TokenResultStatus.FAIL: case TokenResultStatus.TOO_MANY_REQUEST: return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); case TokenResultStatus.BLOCKED: default: return false; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.Comparator; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** * Comparator for flow rules. * * @author jialiang.linjl */ public class FlowRuleComparator implements Comparator { @Override public int compare(FlowRule o1, FlowRule o2) { // the FlowRule in Clustered mode will be put at the end. if (o1.isClusterMode() && !o2.isClusterMode()) { return 1; } if (!o1.isClusterMode() && o2.isClusterMode()) { return -1; } if (o1.getLimitApp() == null) { return 0; } if (o1.getLimitApp().equals(o2.getLimitApp())) { return 0; } if (RuleConstant.LIMIT_APP_DEFAULT.equals(o1.getLimitApp())) { return 1; } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) { return -1; } else { return 0; } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** *

      * One resources can have multiple rules. And these rules take effects in the following order: *

        *
      1. requests from specified caller
      2. *
      3. no specified caller
      4. *
      *

      * * @author jialiang.linjl * @author Eric Zhao * @author Weihua */ public class FlowRuleManager { private static volatile RuleManager flowRules = new RuleManager<>(); private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); /** the corePool size of SCHEDULER must be set at 1, so the two task ({@link #startMetricTimerListener()} can run orderly by the SCHEDULER **/ @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-metrics-record-task", true)); static { currentProperty.addListener(LISTENER); startMetricTimerListener(); } /** *

      Start the MetricTimerListener *

        *
      1. If the flushInterval more than 0, * the timer will run with the flushInterval as the rate
      2. . *
      3. If the flushInterval less than 0(include) or value is not valid, * then means the timer will not be started
      4. *

          */ private static void startMetricTimerListener() { long flushInterval = SentinelConfig.metricLogFlushIntervalSec(); if (flushInterval <= 0) { RecordLog.info("[FlowRuleManager] The MetricTimerListener isn't started. If you want to start it, " + "please change the value(current: {}) of config({}) more than 0 to start it.", flushInterval, SentinelConfig.METRIC_FLUSH_INTERVAL); return; } SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, flushInterval, TimeUnit.SECONDS); } /** * Listen to the {@link SentinelProperty} for {@link FlowRule}s. The property is the source of {@link FlowRule}s. * Flow rules can also be set by {@link #loadRules(List)} directly. * * @param property the property to listen. */ public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager"); currentProperty.removeListener(LISTENER); property.addListener(LISTENER); currentProperty = property; } } /** * Get a copy of the rules. * * @return a new copy of the rules. */ public static List getRules() { return flowRules.getRules(); } /** * Load {@link FlowRule}s, former rules will be replaced. * * @param rules new rules to load. */ public static void loadRules(List rules) { currentProperty.updateValue(rules); } static List getFlowRules(String resource) { return flowRules.getRules(resource); } public static boolean hasConfig(String resource) { return flowRules.hasConfig(resource); } public static boolean isOtherOrigin(String origin, String resourceName) { if (StringUtil.isEmpty(origin)) { return false; } List rules = flowRules.getRules(resourceName); if (rules != null) { for (FlowRule rule : rules) { if (origin.equals(rule.getLimitApp())) { return false; } } } return true; } private static final class FlowPropertyListener implements PropertyListener> { @Override public synchronized void configUpdate(List value) { Map> rules = FlowRuleUtil.buildFlowRuleMap(value); flowRules.updateRules(rules); RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules); } @Override public synchronized void configLoad(List conf) { Map> rules = FlowRuleUtil.buildFlowRuleMap(conf); flowRules.updateRules(rules); RecordLog.info("[FlowRuleManager] Flow rules loaded: {}", rules); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; import com.alibaba.csp.sentinel.slots.block.flow.controller.ThrottlingController; import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController; import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; /** * @author Eric Zhao * @since 1.4.0 */ public final class FlowRuleUtil { /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * * @param list raw list of flow rules * @return constructed new flow rule map; empty map if list is null or empty, or no valid rules */ public static Map> buildFlowRuleMap(List list) { return buildFlowRuleMap(list, null); } /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * * @param list raw list of flow rules * @param filter rule filter * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules */ public static Map> buildFlowRuleMap(List list, Predicate filter) { return buildFlowRuleMap(list, filter, true); } /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * * @param list raw list of flow rules * @param filter rule filter * @param shouldSort whether the rules should be sorted * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules */ public static Map> buildFlowRuleMap(List list, Predicate filter, boolean shouldSort) { return buildFlowRuleMap(list, extractResource, filter, shouldSort); } /** * Build the flow rule map from raw list of flow rules, grouping by provided group function. * * @param list raw list of flow rules * @param groupFunction grouping function of the map (by key) * @param filter rule filter * @param shouldSort whether the rules should be sorted * @param type of key * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules */ public static Map> buildFlowRuleMap(List list, Function groupFunction, Predicate filter, boolean shouldSort) { Map> newRuleMap = new ConcurrentHashMap<>(); if (list == null || list.isEmpty()) { return newRuleMap; } Map> tmpMap = new ConcurrentHashMap<>(); for (FlowRule rule : list) { if (!isValidRule(rule)) { RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); continue; } if (filter != null && !filter.test(rule)) { continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } TrafficShapingController rater = generateRater(rule); rule.setRater(rater); K key = groupFunction.apply(rule); if (key == null) { continue; } Set flowRules = tmpMap.get(key); if (flowRules == null) { // Use hash set here to remove duplicate rules. flowRules = new HashSet<>(); tmpMap.put(key, flowRules); } flowRules.add(rule); } Comparator comparator = new FlowRuleComparator(); for (Entry> entries : tmpMap.entrySet()) { List rules = new ArrayList<>(entries.getValue()); if (shouldSort) { // Sort the rules. Collections.sort(rules, comparator); } newRuleMap.put(entries.getKey(), rules); } return newRuleMap; } private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) { if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { switch (rule.getControlBehavior()) { case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), ColdFactorProperty.coldFactor); case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: return new ThrottlingController(rule.getMaxQueueingTimeMs(), rule.getCount()); case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(), rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); case RuleConstant.CONTROL_BEHAVIOR_DEFAULT: default: // Default mode or unknown mode: default traffic shaping controller (fast-reject). } } return new DefaultController(rule.getCount(), rule.getGrade()); } /** * Check whether provided ID can be a valid cluster flow ID. * * @param id flow ID to check * @return true if valid, otherwise false */ public static boolean validClusterRuleId(Long id) { return id != null && id > 0; } /** * Check whether provided flow rule is valid. * * @param rule flow rule to check * @return true if valid, otherwise false */ public static boolean isValidRule(FlowRule rule) { boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; if (!baseValid) { return false; } if (!checkRegexField(rule)) { return false; } if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { // Check strategy and control (shaping) behavior. return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule); } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) { return checkClusterConcurrentField(rule); } else { return false; } } public static boolean checkClusterConcurrentField(/*@NonNull*/ FlowRule rule) { if (!rule.isClusterMode()) { return true; } ClusterFlowConfig clusterConfig = rule.getClusterConfig(); if (clusterConfig == null) { return false; } if (clusterConfig.getClientOfflineTime() <= 0 || clusterConfig.getResourceTimeout() <= 0) { return false; } if (clusterConfig.getAcquireRefuseStrategy() < 0 || clusterConfig.getResourceTimeoutStrategy() < 0) { return false; } if (!validClusterRuleId(clusterConfig.getFlowId())) { return false; } return isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()); } private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) { if (!rule.isClusterMode()) { return true; } ClusterFlowConfig clusterConfig = rule.getClusterConfig(); if (clusterConfig == null) { return false; } if (!validClusterRuleId(clusterConfig.getFlowId())) { return false; } if (!isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) { return false; } switch (clusterConfig.getStrategy()) { case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL: return true; default: return false; } } public static boolean isWindowConfigValid(int sampleCount, int windowIntervalMs) { return sampleCount > 0 && windowIntervalMs > 0 && windowIntervalMs % sampleCount == 0; } private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) { if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) { return StringUtil.isNotBlank(rule.getRefResource()); } return true; } private static boolean checkRegexField(FlowRule rule) { if (!RuleManager.checkRegexResourceField(rule)) { return false; } if (rule.isRegex()) { return !rule.isClusterMode() && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_DEFAULT; } return true; } private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) { switch (rule.getControlBehavior()) { case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: return rule.getWarmUpPeriodSec() > 0; case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: return rule.getMaxQueueingTimeMs() > 0; case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0; default: return true; } } private static final Function extractResource = new Function() { @Override public String apply(FlowRule rule) { return rule.getResource(); } }; private FlowRuleUtil() { } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Function; import java.util.Collection; /** *

          * Combined the runtime statistics collected from the previous * slots (NodeSelectorSlot, ClusterNodeBuilderSlot, and StatisticSlot), FlowSlot * will use pre-set rules to decide whether the incoming requests should be * blocked. *

          * *

          * {@code SphU.entry(resourceName)} will throw {@code FlowException} if any rule is * triggered. Users can customize their own logic by catching {@code FlowException}. *

          * *

          * One resource can have multiple flow rules. FlowSlot traverses these rules * until one of them is triggered or all rules have been traversed. *

          * *

          * Each {@link FlowRule} is mainly composed of these factors: grade, strategy, path. We * can combine these factors to achieve different effects. *

          * *

          * The grade is defined by the {@code grade} field in {@link FlowRule}. Here, 0 for thread * isolation and 1 for request count shaping (QPS). Both thread count and request * count are collected in real runtime, and we can view these statistics by * following command: *

          * *
           * curl http://localhost:8719/tree
           *
           * idx id    thread pass  blocked   success total aRt   1m-pass   1m-block   1m-all   exception
           * 2   abc647 0      460    46          46   1    27      630       276        897      0
           * 
          * *
            *
          • {@code thread} for the count of threads that is currently processing the resource
          • *
          • {@code pass} for the count of incoming request within one second
          • *
          • {@code blocked} for the count of requests blocked within one second
          • *
          • {@code success} for the count of the requests successfully handled by Sentinel within one second
          • *
          • {@code RT} for the average response time of the requests within a second
          • *
          • {@code total} for the sum of incoming requests and blocked requests within one second
          • *
          • {@code 1m-pass} is for the count of incoming requests within one minute
          • *
          • {@code 1m-block} is for the count of a request blocked within one minute
          • *
          • {@code 1m-all} is the total of incoming and blocked requests within one minute
          • *
          • {@code exception} is for the count of business (customized) exceptions in one second
          • *
          * * This stage is usually used to protect resources from occupying. If a resource * takes long time to finish, threads will begin to occupy. The longer the * response takes, the more threads occupy. * * Besides counter, thread pool or semaphore can also be used to achieve this. * * - Thread pool: Allocate a thread pool to handle these resource. When there is * no more idle thread in the pool, the request is rejected without affecting * other resources. * * - Semaphore: Use semaphore to control the concurrent count of the threads in * this resource. * * The benefit of using thread pool is that, it can walk away gracefully when * time out. But it also bring us the cost of context switch and additional * threads. If the incoming requests is already served in a separated thread, * for instance, a Servlet HTTP request, it will almost double the threads count if * using thread pool. * *

          Traffic Shaping

          *

          * When QPS exceeds the threshold, Sentinel will take actions to control the incoming request, * and is configured by {@code controlBehavior} field in flow rules. *

          *
            *
          1. Immediately reject ({@code RuleConstant.CONTROL_BEHAVIOR_DEFAULT})
          2. *

            * This is the default behavior. The exceeded request is rejected immediately * and the FlowException is thrown *

            * *
          3. Warmup ({@code RuleConstant.CONTROL_BEHAVIOR_WARM_UP})
          4. *

            * If the load of system has been low for a while, and a large amount of * requests comes, the system might not be able to handle all these requests at * once. However if we steady increase the incoming request, the system can warm * up and finally be able to handle all the requests. * This warmup period can be configured by setting the field {@code warmUpPeriodSec} in flow rules. *

            * *
          5. Uniform Rate Limiting ({@code RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER})
          6. *

            * This strategy strictly controls the interval between requests. * In other words, it allows requests to pass at a stable, uniform rate. *

            * *

            * This strategy is an implement of leaky bucket. * It is used to handle the request at a stable rate and is often used in burst traffic (e.g. message handling). * When a large number of requests beyond the system’s capacity arrive * at the same time, the system using this strategy will handle requests and its * fixed rate until all the requests have been processed or time out. *

            *
          * * @author jialiang.linjl * @author Eric Zhao */ @Spi(order = Constants.ORDER_FLOW_SLOT) public class FlowSlot extends AbstractLinkedProcessorSlot { private final FlowRuleChecker checker; public FlowSlot() { this(new FlowRuleChecker()); } /** * Package-private for test. * * @param checker flow rule checker * @since 1.6.1 */ FlowSlot(FlowRuleChecker checker) { AssertUtil.notNull(checker, "flow checker should not be null"); this.checker = checker; } @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkFlow(resourceWrapper, context, node, count, prioritized); fireEntry(context, resourceWrapper, node, count, prioritized, args); } void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException { checker.checkFlow(ruleProvider, resource, context, node, count, prioritized); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } private final Function> ruleProvider = new Function>() { @Override public Collection apply(String resource) { return FlowRuleManager.getFlowRules(resource); } }; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; /** * An exception that marks previous prioritized request has been waiting till now, then should pass. * * @author jialiang.linjl * @since 1.5.0 */ public class PriorityWaitException extends RuntimeException { private final long waitInMs; public PriorityWaitException(long waitInMs) { this.waitInMs = waitInMs; } public long getWaitInMs() { return waitInMs; } @Override public Throwable fillInStackTrace() { return this; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import com.alibaba.csp.sentinel.node.Node; /** * A universal interface for traffic shaping controller. * * @author jialiang.linjl */ public interface TrafficShapingController { /** * Check whether given resource entry can pass with provided count. * * @param node resource node * @param acquireCount count to acquire * @param prioritized whether the request is prioritized * @return true if the resource entry can pass; false if it should be blocked */ boolean canPass(Node node, int acquireCount, boolean prioritized); /** * Check whether given resource entry can pass with provided count. * * @param node resource node * @param acquireCount count to acquire * @return true if the resource entry can pass; false if it should be blocked */ boolean canPass(Node node, int acquireCount); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.OccupyTimeoutProperty; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Default throttling controller (immediately reject strategy). * * @author jialiang.linjl * @author Eric Zhao */ public class DefaultController implements TrafficShapingController { private static final int DEFAULT_AVG_USED_TOKENS = 0; private double count; private int grade; public DefaultController(double count, int grade) { this.count = count; this.grade = grade; } @Override public boolean canPass(Node node, int acquireCount) { return canPass(node, acquireCount, false); } @Override public boolean canPass(Node node, int acquireCount, boolean prioritized) { int curCount = avgUsedTokens(node); if (curCount + acquireCount > count) { if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) { long currentTime; long waitInMs; currentTime = TimeUtil.currentTimeMillis(); waitInMs = node.tryOccupyNext(currentTime, acquireCount, count); if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) { node.addWaitingRequest(currentTime + waitInMs, acquireCount); node.addOccupiedPass(acquireCount); sleep(waitInMs); // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}. throw new PriorityWaitException(waitInMs); } } return false; } return true; } private int avgUsedTokens(Node node) { if (node == null) { return DEFAULT_AVG_USED_TOKENS; } return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps()); } private void sleep(long timeMillis) { try { Thread.sleep(timeMillis); } catch (InterruptedException e) { // Ignore. } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/ThrottlingController.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author Eric Zhao * @author jialiang.linjl * @since 2.0 */ public class ThrottlingController implements TrafficShapingController { // Refactored from legacy RateLimitController of Sentinel 1.x. private static final long MS_TO_NS_OFFSET = TimeUnit.MILLISECONDS.toNanos(1); private final int maxQueueingTimeMs; private final int statDurationMs; private final double count; private final boolean useNanoSeconds; private final AtomicLong latestPassedTime = new AtomicLong(-1); public ThrottlingController(int queueingTimeoutMs, double maxCountPerStat) { this(queueingTimeoutMs, maxCountPerStat, 1000); } public ThrottlingController(int queueingTimeoutMs, double maxCountPerStat, int statDurationMs) { AssertUtil.assertTrue(statDurationMs > 0, "statDurationMs should be positive"); AssertUtil.assertTrue(maxCountPerStat >= 0, "maxCountPerStat should be >= 0"); AssertUtil.assertTrue(queueingTimeoutMs >= 0, "queueingTimeoutMs should be >= 0"); this.maxQueueingTimeMs = queueingTimeoutMs; this.count = maxCountPerStat; this.statDurationMs = statDurationMs; // Use nanoSeconds when durationMs%count != 0 or count/durationMs> 1 (to be accurate) if (maxCountPerStat > 0) { this.useNanoSeconds = statDurationMs % Math.round(maxCountPerStat) != 0 || maxCountPerStat / statDurationMs > 1; } else { this.useNanoSeconds = false; } } @Override public boolean canPass(Node node, int acquireCount) { return canPass(node, acquireCount, false); } private boolean checkPassUsingNanoSeconds(int acquireCount, double maxCountPerStat) { final long maxQueueingTimeNs = maxQueueingTimeMs * MS_TO_NS_OFFSET; long currentTime = System.nanoTime(); // Calculate the interval between every two requests. final long costTimeNs = Math.round(1.0d * MS_TO_NS_OFFSET * statDurationMs * acquireCount / maxCountPerStat); // Expected pass time of this request. long expectedTime = costTimeNs + latestPassedTime.get(); if (expectedTime <= currentTime) { // Contention may exist here, but it's okay. latestPassedTime.set(currentTime); return true; } else { final long curNanos = System.nanoTime(); // Calculate the time to wait. long waitTime = costTimeNs + latestPassedTime.get() - curNanos; if (waitTime > maxQueueingTimeNs) { return false; } long oldTime = latestPassedTime.addAndGet(costTimeNs); waitTime = oldTime - curNanos; if (waitTime > maxQueueingTimeNs) { latestPassedTime.addAndGet(-costTimeNs); return false; } // in race condition waitTime may <= 0 if (waitTime > 0) { sleepNanos(waitTime); } return true; } } private boolean checkPassUsingCachedMs(int acquireCount, double maxCountPerStat) { long currentTime = TimeUtil.currentTimeMillis(); // Calculate the interval between every two requests. long costTime = Math.round(1.0d * statDurationMs * acquireCount / maxCountPerStat); // Expected pass time of this request. long expectedTime = costTime + latestPassedTime.get(); if (expectedTime <= currentTime) { // Contention may exist here, but it's okay. latestPassedTime.set(currentTime); return true; } else { // Calculate the time to wait. long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis(); if (waitTime > maxQueueingTimeMs) { return false; } long oldTime = latestPassedTime.addAndGet(costTime); waitTime = oldTime - TimeUtil.currentTimeMillis(); if (waitTime > maxQueueingTimeMs) { latestPassedTime.addAndGet(-costTime); return false; } // in race condition waitTime may <= 0 if (waitTime > 0) { sleepMs(waitTime); } return true; } } @Override public boolean canPass(Node node, int acquireCount, boolean prioritized) { // Pass when acquire count is less or equal than 0. if (acquireCount <= 0) { return true; } // Reject when count is less or equal than 0. // Otherwise, the costTime will be max of long and waitTime will overflow in some cases. if (count <= 0) { return false; } if (useNanoSeconds) { return checkPassUsingNanoSeconds(acquireCount, this.count); } else { return checkPassUsingCachedMs(acquireCount, this.count); } } private void sleepMs(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { } } private void sleepNanos(long ns) { LockSupport.parkNanos(ns); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; /** *

          * The principle idea comes from Guava. However, the calculation of Guava is * rate-based, which means that we need to translate rate to QPS. *

          * *

          * Requests arriving at the pulse may drag down long idle systems even though it * has a much larger handling capability in stable period. It usually happens in * scenarios that require extra time for initialization, e.g. DB establishes a connection, * connects to a remote service, and so on. That’s why we need “warm up”. *

          * *

          * Sentinel's "warm-up" implementation is based on the Guava's algorithm. * However, Guava’s implementation focuses on adjusting the request interval, * which is similar to leaky bucket. Sentinel pays more attention to * controlling the count of incoming requests per second without calculating its interval, * which resembles token bucket algorithm. *

          * *

          * The remaining tokens in the bucket is used to measure the system utility. * Suppose a system can handle b requests per second. Every second b tokens will * be added into the bucket until the bucket is full. And when system processes * a request, it takes a token from the bucket. The more tokens left in the * bucket, the lower the utilization of the system; when the token in the token * bucket is above a certain threshold, we call it in a "saturation" state. *

          * *

          * Base on Guava’s theory, there is a linear equation we can write this in the * form y = m * x + b where y (a.k.a y(x)), or qps(q)), is our expected QPS * given a saturated period (e.g. 3 minutes in), m is the rate of change from * our cold (minimum) rate to our stable (maximum) rate, x (or q) is the * occupied token. *

          * * @author jialiang.linjl */ public class WarmUpController implements TrafficShapingController { protected double count; private int coldFactor; protected int warningToken = 0; private int maxToken; protected double slope; protected AtomicLong storedTokens = new AtomicLong(0); protected AtomicLong lastFilledTime = new AtomicLong(0); public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) { construct(count, warmUpPeriodInSec, coldFactor); } public WarmUpController(double count, int warmUpPeriodInSec) { construct(count, warmUpPeriodInSec, 3); } private void construct(double count, int warmUpPeriodInSec, int coldFactor) { if (coldFactor <= 1) { throw new IllegalArgumentException("Cold factor should be larger than 1"); } this.count = count; this.coldFactor = coldFactor; // thresholdPermits = 0.5 * warmupPeriod / stableInterval. // warningToken = 100; warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1); // / maxPermits = thresholdPermits + 2 * warmupPeriod / // (stableInterval + coldInterval) // maxToken = 200 maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor)); // slope // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits // - thresholdPermits); slope = (coldFactor - 1.0) / count / (maxToken - warningToken); } @Override public boolean canPass(Node node, int acquireCount) { return canPass(node, acquireCount, false); } @Override public boolean canPass(Node node, int acquireCount, boolean prioritized) { long passQps = (long) node.passQps(); long previousQps = (long) node.previousPassQps(); syncToken(previousQps); // 开始计算它的斜率 // 如果进入了警戒线,开始调整他的qps long restToken = storedTokens.get(); if (restToken >= warningToken) { long aboveToken = restToken - warningToken; // 消耗的速度要比warning快,但是要比慢 // current interval = restToken*slope+1/count double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); if (passQps + acquireCount <= warningQps) { return true; } } else { if (passQps + acquireCount <= count) { return true; } } return false; } protected void syncToken(long passQps) { long currentTime = TimeUtil.currentTimeMillis(); currentTime = currentTime - currentTime % 1000; long oldLastFillTime = lastFilledTime.get(); if (currentTime <= oldLastFillTime) { return; } long oldValue = storedTokens.get(); long newValue = coolDownTokens(currentTime, passQps); if (storedTokens.compareAndSet(oldValue, newValue)) { long currentValue = storedTokens.addAndGet(0 - passQps); if (currentValue < 0) { storedTokens.set(0L); } lastFilledTime.set(currentTime); } } private long coolDownTokens(long currentTime, long passQps) { long oldValue = storedTokens.get(); long newValue = oldValue; // 添加令牌的判断前提条件: // 当令牌的消耗程度远远低于警戒线的时候 if (oldValue < warningToken) { newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); } else if (oldValue > warningToken) { if (passQps < (int)count / coldFactor) { newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); } } return Math.min(newValue, maxToken); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author jialiang.linjl * @since 1.4.0 */ public class WarmUpRateLimiterController extends WarmUpController { private final int timeoutInMs; private final AtomicLong latestPassedTime = new AtomicLong(-1); public WarmUpRateLimiterController(double count, int warmUpPeriodSec, int timeOutMs, int coldFactor) { super(count, warmUpPeriodSec, coldFactor); this.timeoutInMs = timeOutMs; } @Override public boolean canPass(Node node, int acquireCount) { return canPass(node, acquireCount, false); } @Override public boolean canPass(Node node, int acquireCount, boolean prioritized) { long previousQps = (long) node.previousPassQps(); syncToken(previousQps); long currentTime = TimeUtil.currentTimeMillis(); long restToken = storedTokens.get(); long costTime = 0; long expectedTime = 0; if (restToken >= warningToken) { long aboveToken = restToken - warningToken; // current interval = restToken*slope+1/count double warmingQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); costTime = Math.round(1.0 * (acquireCount) / warmingQps * 1000); } else { costTime = Math.round(1.0 * (acquireCount) / count * 1000); } expectedTime = costTime + latestPassedTime.get(); if (expectedTime <= currentTime) { latestPassedTime.set(currentTime); return true; } else { long waitTime = costTime + latestPassedTime.get() - currentTime; if (waitTime > timeoutInMs) { return false; } else { long oldTime = latestPassedTime.addAndGet(costTime); try { waitTime = oldTime - TimeUtil.currentTimeMillis(); if (waitTime > timeoutInMs) { latestPassedTime.addAndGet(-costTime); return false; } if (waitTime > 0) { Thread.sleep(waitTime); } return true; } catch (InterruptedException e) { } } } return false; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/tokenbucket/AbstractTokenBucket.java ================================================ /* * Copyright 1999-2023 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.tokenbucket; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author LearningGp */ public class AbstractTokenBucket implements TokenBucket{ protected final long MAX_UNIT_PRODUCE_NUM = Long.MAX_VALUE; /** * Number of tokens left in the bucket */ protected volatile long currentTokenNum; /** * Time of next production token */ protected volatile long nextProduceTime; /** * Number of tokens produced per unit of time */ protected final long unitProduceNum; /** * Maximum number of tokens stored in the bucket */ protected final long maxTokenNum; protected final long intervalInMs; protected final long startTime; public AbstractTokenBucket(long unitProduceNum, long maxTokenNum, boolean fullStart, long intervalInMs) { AssertUtil.isTrue(unitProduceNum > 0 && intervalInMs > 0 && unitProduceNum < MAX_UNIT_PRODUCE_NUM, "Illegal unitProduceNum or intervalInSeconds"); AssertUtil.isTrue(maxTokenNum > 0, "Illegal maxTokenNum"); this.unitProduceNum = unitProduceNum; this.maxTokenNum = maxTokenNum; this.intervalInMs = intervalInMs; this.startTime = TimeUtil.currentTimeMillis(); this.nextProduceTime = startTime; if (fullStart) { this.currentTokenNum = maxTokenNum; } else { //The token will be filled when the first request arrives (including the initial token) this.currentTokenNum = 0; } } @Override public boolean tryConsume(long tokenNum) { if (tokenNum <= 0) { return true; } if (tokenNum > maxTokenNum) { return false; } long currentTimestamp = TimeUtil.currentTimeMillis(); refreshCurrentTokenNum(currentTimestamp); if (tokenNum <= currentTokenNum) { currentTokenNum -= tokenNum; return true; } else { return false; } } @Override public void refreshCurrentTokenNum(long currentTimestamp) { if (nextProduceTime > currentTimestamp) { return; } currentTokenNum = Math.min(maxTokenNum, currentTokenNum + calProducedTokenNum(currentTimestamp)); updateNextProduceTime(currentTimestamp); } protected long calProducedTokenNum(long currentTimestamp) { if (nextProduceTime > currentTimestamp) { return 0; } long nextRefreshUnitCount = (nextProduceTime - startTime) / intervalInMs; long currentUnitCount = (currentTimestamp - startTime) / intervalInMs; long unitCount = currentUnitCount - nextRefreshUnitCount + 1; return unitCount * unitProduceNum; } protected void updateNextProduceTime(long currentTimestamp) { nextProduceTime = intervalInMs - ((currentTimestamp - startTime) % intervalInMs) + currentTimestamp; } public long refreshTokenAndGetCurrentTokenNum() { refreshCurrentTokenNum(TimeUtil.currentTimeMillis()); return currentTokenNum; } public long getCurrentTokenNum() { return currentTokenNum; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/tokenbucket/DefaultTokenBucket.java ================================================ /* * Copyright 1999-2023 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.tokenbucket; /** * @author LearningGp */ public class DefaultTokenBucket extends AbstractTokenBucket{ public DefaultTokenBucket(long unitProduceNum, long maxTokenNum, long intervalInMs){ super(unitProduceNum, maxTokenNum, false, intervalInMs); } public DefaultTokenBucket(long unitProduceNum, long maxTokenNum, boolean fullStart, long intervalInMs){ super(unitProduceNum, maxTokenNum, fullStart, intervalInMs); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/tokenbucket/StrictTokenBucket.java ================================================ /* * Copyright 1999-2023 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.tokenbucket; import com.alibaba.csp.sentinel.util.TimeUtil; /** * @author LearningGp */ public class StrictTokenBucket extends AbstractTokenBucket{ final private Object refreshLock = new Object(); final private Object consumeLock = new Object(); public StrictTokenBucket(long unitProduceNum, long maxTokenNum, long intervalInMs) { super(unitProduceNum, maxTokenNum, false, intervalInMs); } public StrictTokenBucket(long unitProduceNum, long maxTokenNum, boolean fullStart, long intervalInMs) { super(unitProduceNum, maxTokenNum, fullStart, intervalInMs); } @Override public boolean tryConsume(long tokenNum) { if (tokenNum > maxTokenNum) { return false; } long currentTimestamp = TimeUtil.currentTimeMillis(); refreshCurrentTokenNum(currentTimestamp); if (tokenNum <= currentTokenNum) { synchronized (consumeLock) { if (tokenNum <= currentTokenNum) { currentTokenNum -= tokenNum; return true; } } } return false; } @Override public void refreshCurrentTokenNum(long currentTimestamp) { if (nextProduceTime > currentTimestamp) { return; } long producedTokenNum = calProducedTokenNum(currentTimestamp); synchronized (refreshLock) { if (nextProduceTime > currentTimestamp) { return; } currentTokenNum = Math.min(maxTokenNum, currentTokenNum + producedTokenNum); updateNextProduceTime(currentTimestamp); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/tokenbucket/TokenBucket.java ================================================ /* * Copyright 1999-2023 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.tokenbucket; /** * @author LearningGp */ public interface TokenBucket { boolean tryConsume(long tokenNum); void refreshCurrentTokenNum(long timestamp); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.clusterbuilder; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.IntervalProperty; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.SampleCountProperty; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.spi.Spi; /** *

          * This slot maintains resource running statistics (response time, qps, thread * count, exception), and a list of callers as well which is marked by * {@link ContextUtil#enter(String origin)} *

          *

          * One resource has only one cluster node, while one resource can have multiple * default nodes. *

          * * @author jialiang.linjl */ @Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT) public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot { /** *

          * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share * the same {@link ProcessorSlotChain} globally, no matter in which context. So if * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)}, * the resource name must be same but context name may not. *

          *

          * To get total statistics of the same resource in different context, same resource * shares the same {@link ClusterNode} globally. All {@link ClusterNode}s are cached * in this map. *

          *

          * The longer the application runs, the more stable this mapping will * become. so we don't concurrent map but a lock. as this lock only happens * at the very beginning while concurrent map will hold the lock all the time. *

          */ private static volatile Map clusterNodeMap = new HashMap<>(); private static final Object lock = new Object(); private volatile ClusterNode clusterNode = null; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { if (clusterNode == null) { synchronized (lock) { if (clusterNode == null) { // Create the cluster node. clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType()); HashMap newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } node.setClusterNode(clusterNode); /* * if context origin is set, we should get or create a new {@link Node} of * the specific origin. */ if (!"".equals(context.getOrigin())) { Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } /** * Get {@link ClusterNode} of the resource of the specific type. * * @param id resource name. * @param type invoke type. * @return the {@link ClusterNode} */ public static ClusterNode getClusterNode(String id, EntryType type) { return clusterNodeMap.get(new StringResourceWrapper(id, type)); } /** * Get {@link ClusterNode} of the resource name. * * @param id resource name. * @return the {@link ClusterNode}. */ public static ClusterNode getClusterNode(String id) { if (id == null) { return null; } ClusterNode clusterNode = null; for (EntryType nodeType : EntryType.values()) { clusterNode = clusterNodeMap.get(new StringResourceWrapper(id, nodeType)); if (clusterNode != null) { break; } } return clusterNode; } /** * Get {@link ClusterNode}s map, this map holds all {@link ClusterNode}s, it's key is resource name, * value is the related {@link ClusterNode}.
          * DO NOT MODIFY the map returned. * * @return all {@link ClusterNode}s */ public static Map getClusterNodeMap() { return clusterNodeMap; } /** * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL} or * {@link SampleCountProperty#SAMPLE_COUNT} is changed. */ public static void resetClusterNodes() { for (ClusterNode node : clusterNodeMap.values()) { node.reset(); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.logger; import com.alibaba.csp.sentinel.eagleeye.EagleEye; import com.alibaba.csp.sentinel.eagleeye.StatLogger; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.util.StringUtil; public class EagleEyeLogUtil { public static final String FILE_NAME = "sentinel-block.log"; private static StatLogger statLogger; static { String path = LogBase.getLogBaseDir() + FILE_NAME; statLogger = EagleEye.statLoggerBuilder("sentinel-block-log") .intervalSeconds(1) .entryDelimiter('|') .keyDelimiter(',') .valueDelimiter(',') .maxEntryCount(6000) .configLogFilePath(path) .maxFileSizeMB(300) .maxBackupIndex(3) .buildSingleton(); } public static void log(String resource, String exceptionName, String ruleLimitApp, String origin, Long ruleId, int count) { String ruleIdString = StringUtil.EMPTY; if (ruleId != null) { ruleIdString = String.valueOf(ruleId); } statLogger.stat(resource, exceptionName, ruleLimitApp, origin, ruleIdString).count(count); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.logger; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.spi.Spi; /** * A {@link com.alibaba.csp.sentinel.slotchain.ProcessorSlot} that is response for logging block exceptions * to provide concrete logs for troubleshooting. */ @Spi(order = Constants.ORDER_LOG_SLOT) public class LogSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args) throws Throwable { try { fireEntry(context, resourceWrapper, obj, count, prioritized, args); } catch (BlockException e) { EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(), context.getOrigin(), e.getRule() != null ? e.getRule().getId() : null, count); throw e; } catch (Throwable e) { RecordLog.warn("Unexpected entry exception", e); } } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { try { fireExit(context, resourceWrapper, count, args); } catch (Throwable e) { RecordLog.warn("Unexpected entry exit exception", e); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.nodeselector; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.spi.Spi; import java.util.HashMap; import java.util.Map; /** *

          * This class will try to build the calling traces via *
            *
          1. adding a new {@link DefaultNode} if needed as the last child in the context. * The context's last node is the current node or the parent node of the context.
          2. *
          3. setting itself to the context current node.
          4. *
          *

          * *

          It works as follow:

          *
           * ContextUtil.enter("entrance1", "appA");
           * Entry nodeA = SphU.entry("nodeA");
           * if (nodeA != null) {
           *     nodeA.exit();
           * }
           * ContextUtil.exit();
           * 
          * * Above code will generate the following invocation structure in memory: * *
           *
           *              machine-root
           *                  /
           *                 /
           *           EntranceNode1
           *               /
           *              /
           *        DefaultNode(nodeA)- - - - - -> ClusterNode(nodeA);
           * 
          * *

          * Here the {@link EntranceNode} represents "entrance1" given by * {@code ContextUtil.enter("entrance1", "appA")}. *

          *

          * Both DefaultNode(nodeA) and ClusterNode(nodeA) holds statistics of "nodeA", which is given * by {@code SphU.entry("nodeA")} *

          *

          * The {@link ClusterNode} is uniquely identified by the ResourceId; the {@link DefaultNode} * is identified by both the resource id and {@link Context}. In other words, one resource * id will generate multiple {@link DefaultNode} for each distinct context, but only one * {@link ClusterNode}. *

          *

          * the following code shows one resource id in two different context: *

          * *
           *    ContextUtil.enter("entrance1", "appA");
           *    Entry nodeA = SphU.entry("nodeA");
           *    if (nodeA != null) {
           *        nodeA.exit();
           *    }
           *    ContextUtil.exit();
           *
           *    ContextUtil.enter("entrance2", "appA");
           *    nodeA = SphU.entry("nodeA");
           *    if (nodeA != null) {
           *        nodeA.exit();
           *    }
           *    ContextUtil.exit();
           * 
          * * Above code will generate the following invocation structure in memory: * *
           *
           *                  machine-root
           *                  /         \
           *                 /           \
           *         EntranceNode1   EntranceNode2
           *               /               \
           *              /                 \
           *      DefaultNode(nodeA)   DefaultNode(nodeA)
           *             |                    |
           *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
           * 
          * *

          * As we can see, two {@link DefaultNode} are created for "nodeA" in two context, but only one * {@link ClusterNode} is created. *

          * *

          * We can also check this structure by calling:
          * {@code curl http://localhost:8719/tree?type=root} *

          * * @author jialiang.linjl * @see EntranceNode * @see ContextUtil */ @Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT) public class NodeSelectorSlot extends AbstractLinkedProcessorSlot { /** * {@link DefaultNode}s of the same resource in different context. */ private volatile Map map = new HashMap(10); @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { /* * It's interesting that we use context name rather resource name as the map key. * * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share * the same {@link ProcessorSlotChain} globally, no matter in which context. So if * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, Object...)}, * the resource name must be same but context name may not. * * If we use {@link com.alibaba.csp.sentinel.SphU#entry(String resource)} to * enter same resource in different context, using context name as map key can * distinguish the same resource. In this case, multiple {@link DefaultNode}s will be created * of the same resource name, for every distinct context (different context name) each. * * Consider another question. One resource may have multiple {@link DefaultNode}, * so what is the fastest way to get total statistics of the same resource? * The answer is all {@link DefaultNode}s with same resource name share one * {@link ClusterNode}. See {@link ClusterBuilderSlot} for detail. */ DefaultNode node = map.get(context.getName()); if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { node = new DefaultNode(resourceWrapper, null); HashMap cacheMap = new HashMap(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; // Build invocation tree ((DefaultNode) context.getLastNode()).addChild(node); } } } context.setCurNode(node); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic; /** * @author Eric Zhao */ public enum MetricEvent { /** * Normal pass. */ PASS, /** * Normal block. */ BLOCK, EXCEPTION, SUCCESS, RT, /** * Passed in future quota (pre-occupied, since 1.5.0). */ OCCUPIED_PASS } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic; import java.util.Collection; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; /** *

          * A processor slot that dedicates to real time statistics. * When entering this slot, we need to separately count the following * information: *

            *
          • {@link ClusterNode}: total statistics of a cluster node of the resource ID.
          • *
          • Origin node: statistics of a cluster node from different callers/origins.
          • *
          • {@link DefaultNode}: statistics for specific resource name in the specific context.
          • *
          • Finally, the sum statistics of all entrances.
          • *
          *

          * * @author jialiang.linjl * @author Eric Zhao */ @Spi(order = Constants.ORDER_STATISTIC_SLOT) public class StatisticSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { try { // Do some checking. fireEntry(context, resourceWrapper, node, count, prioritized, args); // Request passed, add thread count and pass count. node.increaseThreadNum(); node.addPassRequest(count); if (context.getCurEntry().getOriginNode() != null) { // Add count for origin node. context.getCurEntry().getOriginNode().increaseThreadNum(); context.getCurEntry().getOriginNode().addPassRequest(count); } if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseThreadNum(); Constants.ENTRY_NODE.addPassRequest(count); } // Handle pass event with registered entry callback handlers. for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onPass(context, resourceWrapper, node, count, args); } } catch (PriorityWaitException ex) { node.increaseThreadNum(); if (context.getCurEntry().getOriginNode() != null) { // Add count for origin node. context.getCurEntry().getOriginNode().increaseThreadNum(); } if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseThreadNum(); } // Handle pass event with registered entry callback handlers. for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onPass(context, resourceWrapper, node, count, args); } } catch (BlockException e) { // Blocked, set block exception to current entry. context.getCurEntry().setBlockError(e); // Add block count. node.increaseBlockQps(count); if (context.getCurEntry().getOriginNode() != null) { context.getCurEntry().getOriginNode().increaseBlockQps(count); } if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseBlockQps(count); } // Handle block event with registered entry callback handlers. for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onBlocked(e, context, resourceWrapper, node, count, args); } throw e; } catch (Throwable e) { // Unexpected internal error, set error to current entry. context.getCurEntry().setError(e); throw e; } } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { Node node = context.getCurNode(); if (context.getCurEntry().getBlockError() == null) { // Calculate response time (use completeStatTime as the time of completion). long completeStatTime = TimeUtil.currentTimeMillis(); context.getCurEntry().setCompleteTimestamp(completeStatTime); long rt = completeStatTime - context.getCurEntry().getCreateTimestamp(); Throwable error = context.getCurEntry().getError(); // Record response time and success count. recordCompleteFor(node, count, rt, error); recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error); if (resourceWrapper.getEntryType() == EntryType.IN) { recordCompleteFor(Constants.ENTRY_NODE, count, rt, error); } } // Handle exit event with registered exit callback handlers. Collection exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks(); for (ProcessorSlotExitCallback handler : exitCallbacks) { handler.onExit(context, resourceWrapper, count, args); } // fix bug https://github.com/alibaba/Sentinel/issues/2374 fireExit(context, resourceWrapper, count, args); } private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) { if (node == null) { return; } node.addRtAndSuccess(rt, batchCount); node.decreaseThreadNum(); if (error != null && !(error instanceof BlockException)) { node.increaseExceptionQps(batchCount); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; /** *

          * Callback registry for {@link StatisticSlot}. Now two kind of callbacks are supported: *

            *
          • {@link ProcessorSlotEntryCallback}: callback for entry (passed and blocked)
          • *
          • {@link ProcessorSlotExitCallback}: callback for exiting {@link StatisticSlot}
          • *
          *

          * * @author Eric Zhao * @since 0.2.0 */ public final class StatisticSlotCallbackRegistry { private static final Map> entryCallbackMap = new ConcurrentHashMap>(); private static final Map exitCallbackMap = new ConcurrentHashMap(); public static void clearEntryCallback() { entryCallbackMap.clear(); } public static void clearExitCallback() { exitCallbackMap.clear(); } public static void addEntryCallback(String key, ProcessorSlotEntryCallback callback) { entryCallbackMap.put(key, callback); } public static void addExitCallback(String key, ProcessorSlotExitCallback callback) { exitCallbackMap.put(key, callback); } public static ProcessorSlotEntryCallback removeEntryCallback(String key) { if (key == null) { return null; } return entryCallbackMap.remove(key); } public static ProcessorSlotExitCallback removeExitCallback(String key) { if (key == null) { return null; } return exitCallbackMap.remove(key); } public static Collection> getEntryCallbacks() { return entryCallbackMap.values(); } public static Collection getExitCallbacks() { return exitCallbackMap.values(); } private StatisticSlotCallbackRegistry() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.base; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.ReentrantLock; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** *

          * Basic data structure for statistic metrics in Sentinel. *

          *

          * Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span, * and the total time span is {@link #intervalInMs}, so the total bucket amount is: * {@code sampleCount = intervalInMs / windowLengthInMs}. *

          * * @param type of statistic data * @author jialiang.linjl * @author Eric Zhao * @author Carpenter Lee */ public abstract class LeapArray { protected int windowLengthInMs; protected int sampleCount; protected int intervalInMs; private double intervalInSecond; protected final AtomicReferenceArray> array; /** * The conditional (predicate) update lock is used only when current bucket is deprecated. */ private final ReentrantLock updateLock = new ReentrantLock(); /** * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. * * @param sampleCount bucket count of the sliding window * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds */ public LeapArray(int sampleCount, int intervalInMs) { AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount); AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive"); AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); this.windowLengthInMs = intervalInMs / sampleCount; this.intervalInMs = intervalInMs; this.intervalInSecond = intervalInMs / 1000.0; this.sampleCount = sampleCount; this.array = new AtomicReferenceArray<>(sampleCount); } /** * Get the bucket at current timestamp. * * @return the bucket at current timestamp */ public WindowWrap currentWindow() { return currentWindow(TimeUtil.currentTimeMillis()); } /** * Create a new statistic value for bucket. * * @param timeMillis current time in milliseconds * @return the new empty bucket */ public abstract T newEmptyBucket(long timeMillis); /** * Reset given bucket to provided start time and reset the value. * * @param startTime the start time of the bucket in milliseconds * @param windowWrap current bucket * @return new clean bucket at given start time */ protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime); private int calculateTimeIdx(/*@Valid*/ long timeMillis) { long timeId = timeMillis / windowLengthInMs; // Calculate current index so we can map the timestamp to the leap array. return (int)(timeId % array.length()); } protected long calculateWindowStart(/*@Valid*/ long timeMillis) { return timeMillis - timeMillis % windowLengthInMs; } /** * Get bucket item at provided timestamp. * * @param timeMillis a valid timestamp in milliseconds * @return current bucket item at provided timestamp if the time is valid; null if time is invalid */ public WindowWrap currentWindow(long timeMillis) { if (timeMillis < 0) { return null; } int idx = calculateTimeIdx(timeMillis); // Calculate current bucket start time. long windowStart = calculateWindowStart(timeMillis); /* * Get bucket item at given time from the array. * * (1) Bucket is absent, then just create a new bucket and CAS update to circular array. * (2) Bucket is up-to-date, then just return the bucket. * (3) Bucket is deprecated, then reset current bucket. */ while (true) { WindowWrap old = array.get(idx); if (old == null) { /* * B0 B1 B2 NULL B4 * ||_______|_______|_______|_______|_______||___ * 200 400 600 800 1000 1200 timestamp * ^ * time=888 * bucket is empty, so create new and update * * If the old bucket is absent, then we create a new bucket at {@code windowStart}, * then try to update circular array via a CAS operation. Only one thread can * succeed to update, while other threads yield its time slice. */ WindowWrap window = new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); if (array.compareAndSet(idx, null, window)) { // Successfully updated, return the created bucket. return window; } else { // Contention failed, the thread will yield its time slice to wait for bucket available. Thread.yield(); } } else if (windowStart == old.windowStart()) { /* * B0 B1 B2 B3 B4 * ||_______|_______|_______|_______|_______||___ * 200 400 600 800 1000 1200 timestamp * ^ * time=888 * startTime of Bucket 3: 800, so it's up-to-date * * If current {@code windowStart} is equal to the start timestamp of old bucket, * that means the time is within the bucket, so directly return the bucket. */ return old; } else if (windowStart > old.windowStart()) { /* * (old) * B0 B1 B2 NULL B4 * |_______||_______|_______|_______|_______|_______||___ * ... 1200 1400 1600 1800 2000 2200 timestamp * ^ * time=1676 * startTime of Bucket 2: 400, deprecated, should be reset * * If the start timestamp of old bucket is behind provided time, that means * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}. * Note that the reset and clean-up operations are hard to be atomic, * so we need a update lock to guarantee the correctness of bucket update. * * The update lock is conditional (tiny scope) and will take effect only when * bucket is deprecated, so in most cases it won't lead to performance loss. */ if (updateLock.tryLock()) { try { // Successfully get the update lock, now we reset the bucket. return resetWindowTo(old, windowStart); } finally { updateLock.unlock(); } } else { // Contention failed, the thread will yield its time slice to wait for bucket available. Thread.yield(); } } else if (windowStart < old.windowStart()) { // Should not go through here, as the provided time is already behind. return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); } } } /** * Get the previous bucket item before provided timestamp. * * @param timeMillis a valid timestamp in milliseconds * @return the previous bucket item before provided timestamp */ public WindowWrap getPreviousWindow(long timeMillis) { if (timeMillis < 0) { return null; } int idx = calculateTimeIdx(timeMillis - windowLengthInMs); timeMillis = timeMillis - windowLengthInMs; WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } if (wrap.windowStart() + windowLengthInMs < (timeMillis)) { return null; } return wrap; } /** * Get the previous bucket item for current timestamp. * * @return the previous bucket item for current timestamp */ public WindowWrap getPreviousWindow() { return getPreviousWindow(TimeUtil.currentTimeMillis()); } /** * Get statistic value from bucket for provided timestamp. * * @param timeMillis a valid timestamp in milliseconds * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null */ public T getWindowValue(long timeMillis) { if (timeMillis < 0) { return null; } int idx = calculateTimeIdx(timeMillis); WindowWrap bucket = array.get(idx); if (bucket == null || !bucket.isTimeInWindow(timeMillis)) { return null; } return bucket.value(); } /** * Check if a bucket is deprecated, which means that the bucket * has been behind for at least an entire window time span. * * @param windowWrap a non-null bucket * @return true if the bucket is deprecated; otherwise false */ public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap); } public boolean isWindowDeprecated(long time, WindowWrap windowWrap) { return time - windowWrap.windowStart() > intervalInMs; } /** * Get valid bucket list for entire sliding window. * The list will only contain "valid" buckets. * * @return valid bucket list for entire sliding window. */ public List> list() { return list(TimeUtil.currentTimeMillis()); } public List> list(long validTime) { int size = array.length(); List> result = new ArrayList>(size); for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) { continue; } result.add(windowWrap); } return result; } /** * Get all buckets for entire sliding window including deprecated buckets. * * @return all buckets for entire sliding window */ public List> listAll() { int size = array.length(); List> result = new ArrayList>(size); for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null) { continue; } result.add(windowWrap); } return result; } /** * Get aggregated value list for entire sliding window. * The list will only contain value from "valid" buckets. * * @return aggregated value list for entire sliding window */ public List values() { return values(TimeUtil.currentTimeMillis()); } public List values(long timeMillis) { if (timeMillis < 0) { return new ArrayList(); } int size = array.length(); List result = new ArrayList(size); for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) { continue; } result.add(windowWrap.value()); } return result; } /** * Get the valid "head" bucket of the sliding window for provided timestamp. * Package-private for test. * * @param timeMillis a valid timestamp in milliseconds * @return the "head" bucket if it exists and is valid; otherwise null */ WindowWrap getValidHead(long timeMillis) { // Calculate index for expected head time. int idx = calculateTimeIdx(timeMillis + windowLengthInMs); WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } return wrap; } /** * Get the valid "head" bucket of the sliding window at current timestamp. * * @return the "head" bucket if it exists and is valid; otherwise null */ public WindowWrap getValidHead() { return getValidHead(TimeUtil.currentTimeMillis()); } /** * Get sample count (total amount of buckets). * * @return sample count */ public int getSampleCount() { return sampleCount; } /** * Get total interval length of the sliding window in milliseconds. * * @return interval in second */ public int getIntervalInMs() { return intervalInMs; } /** * Get total interval length of the sliding window. * * @return interval in second */ public double getIntervalInSecond() { return intervalInSecond; } public void debug(long time) { StringBuilder sb = new StringBuilder(); List> lists = list(time); sb.append("Thread_").append(Thread.currentThread().getId()).append("_"); for (WindowWrap window : lists) { sb.append(window.windowStart()).append(":").append(window.value().toString()); } System.out.println(sb.toString()); } public long currentWaiting() { // TODO: default method. Should remove this later. return 0; } public void addWaiting(long time, int acquireCount) { // Do nothing by default. throw new UnsupportedOperationException(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.base; import java.util.concurrent.atomic.LongAdder; /** * @author Eric Zhao */ public class UnaryLeapArray extends LeapArray { public UnaryLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); } @Override public LongAdder newEmptyBucket(long time) { return new LongAdder(); } @Override protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { windowWrap.resetTo(startTime); windowWrap.value().reset(); return windowWrap; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.base; /** * Wrapper entity class for a period of time window. * * @param data type * @author jialiang.linjl * @author Eric Zhao */ public class WindowWrap { /** * Time length of a single window bucket in milliseconds. */ private final long windowLengthInMs; /** * Start timestamp of the window in milliseconds. */ private long windowStart; /** * Statistic data. */ private T value; /** * @param windowLengthInMs a single window bucket's time length in milliseconds. * @param windowStart the start timestamp of the window * @param value statistic data */ public WindowWrap(long windowLengthInMs, long windowStart, T value) { this.windowLengthInMs = windowLengthInMs; this.windowStart = windowStart; this.value = value; } public long windowLength() { return windowLengthInMs; } public long windowStart() { return windowStart; } public T value() { return value; } public void setValue(T value) { this.value = value; } /** * Reset start timestamp of current bucket to provided time. * * @param startTime valid start timestamp * @return bucket after reset */ public WindowWrap resetTo(long startTime) { this.windowStart = startTime; return this; } /** * Check whether given timestamp is in current bucket. * * @param timeMillis valid timestamp in ms * @return true if the given time is in current bucket, otherwise false * @since 1.5.0 */ public boolean isTimeInWindow(long timeMillis) { return windowStart <= timeMillis && timeMillis < windowStart + windowLengthInMs; } @Override public String toString() { return "WindowWrap{" + "windowLengthInMs=" + windowLengthInMs + ", windowStart=" + windowStart + ", value=" + value + '}'; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.data; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; import java.util.concurrent.atomic.LongAdder; /** * Represents metrics data in a period of time span. * * @author jialiang.linjl * @author Eric Zhao */ public class MetricBucket { private final LongAdder[] counters; private volatile long minRt; public MetricBucket() { MetricEvent[] events = MetricEvent.values(); this.counters = new LongAdder[events.length]; for (MetricEvent event : events) { counters[event.ordinal()] = new LongAdder(); } initMinRt(); } public MetricBucket reset(MetricBucket bucket) { for (MetricEvent event : MetricEvent.values()) { counters[event.ordinal()].reset(); counters[event.ordinal()].add(bucket.get(event)); } initMinRt(); return this; } private void initMinRt() { this.minRt = SentinelConfig.statisticMaxRt(); } /** * Reset the adders. * * @return new metric bucket in initial state */ public MetricBucket reset() { for (MetricEvent event : MetricEvent.values()) { counters[event.ordinal()].reset(); } initMinRt(); return this; } public long get(MetricEvent event) { return counters[event.ordinal()].sum(); } public MetricBucket add(MetricEvent event, long n) { counters[event.ordinal()].add(n); return this; } public long pass() { return get(MetricEvent.PASS); } public long occupiedPass() { return get(MetricEvent.OCCUPIED_PASS); } public long block() { return get(MetricEvent.BLOCK); } public long exception() { return get(MetricEvent.EXCEPTION); } public long rt() { return get(MetricEvent.RT); } public long minRt() { return minRt; } public long success() { return get(MetricEvent.SUCCESS); } public void addPass(int n) { add(MetricEvent.PASS, n); } public void addOccupiedPass(int n) { add(MetricEvent.OCCUPIED_PASS, n); } public void addException(int n) { add(MetricEvent.EXCEPTION, n); } public void addBlock(int n) { add(MetricEvent.BLOCK, n); } public void addSuccess(int n) { add(MetricEvent.SUCCESS, n); } public void addRT(long rt) { add(MetricEvent.RT, rt); // Not thread-safe, but it's okay. if (rt < minRt) { minRt = rt; } } @Override public String toString() { return "p: " + pass() + ", b: " + block() + ", w: " + occupiedPass(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray; import com.alibaba.csp.sentinel.util.function.Predicate; /** * The basic metric class in Sentinel using a {@link BucketLeapArray} internal. * * @author jialiang.linjl * @author Eric Zhao */ public class ArrayMetric implements Metric { private final LeapArray data; public ArrayMetric(int sampleCount, int intervalInMs) { this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); } public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) { if (enableOccupy) { this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); } else { this.data = new BucketLeapArray(sampleCount, intervalInMs); } } /** * For unit test. */ public ArrayMetric(LeapArray array) { this.data = array; } @Override public long success() { data.currentWindow(); long success = 0; List list = data.values(); for (MetricBucket window : list) { success += window.success(); } return success; } @Override public long maxSuccess() { data.currentWindow(); long success = 0; List list = data.values(); for (MetricBucket window : list) { if (window.success() > success) { success = window.success(); } } return Math.max(success, 1); } @Override public long exception() { data.currentWindow(); long exception = 0; List list = data.values(); for (MetricBucket window : list) { exception += window.exception(); } return exception; } @Override public long block() { data.currentWindow(); long block = 0; List list = data.values(); for (MetricBucket window : list) { block += window.block(); } return block; } @Override public long pass() { data.currentWindow(); long pass = 0; List list = data.values(); for (MetricBucket window : list) { pass += window.pass(); } return pass; } @Override public long occupiedPass() { data.currentWindow(); long pass = 0; List list = data.values(); for (MetricBucket window : list) { pass += window.occupiedPass(); } return pass; } @Override public long rt() { data.currentWindow(); long rt = 0; List list = data.values(); for (MetricBucket window : list) { rt += window.rt(); } return rt; } @Override public long minRt() { data.currentWindow(); long rt = SentinelConfig.statisticMaxRt(); List list = data.values(); for (MetricBucket window : list) { if (window.minRt() < rt) { rt = window.minRt(); } } return Math.max(1, rt); } @Override public List details() { List details = new ArrayList<>(); data.currentWindow(); List> list = data.list(); for (WindowWrap window : list) { if (window == null) { continue; } details.add(fromBucket(window)); } return details; } @Override public List detailsOnCondition(Predicate timePredicate) { List details = new ArrayList<>(); data.currentWindow(); List> list = data.list(); for (WindowWrap window : list) { if (window == null) { continue; } if (timePredicate != null && !timePredicate.test(window.windowStart())) { continue; } details.add(fromBucket(window)); } return details; } private MetricNode fromBucket(WindowWrap wrap) { MetricNode node = new MetricNode(); node.setBlockQps(wrap.value().block()); node.setExceptionQps(wrap.value().exception()); node.setPassQps(wrap.value().pass()); long successQps = wrap.value().success(); node.setSuccessQps(successQps); if (successQps != 0) { node.setRt(wrap.value().rt() / successQps); } else { node.setRt(wrap.value().rt()); } node.setTimestamp(wrap.windowStart()); node.setOccupiedPassQps(wrap.value().occupiedPass()); return node; } @Override public MetricBucket[] windows() { data.currentWindow(); return data.values().toArray(new MetricBucket[0]); } @Override public void addException(int count) { WindowWrap wrap = data.currentWindow(); wrap.value().addException(count); } @Override public void addBlock(int count) { WindowWrap wrap = data.currentWindow(); wrap.value().addBlock(count); } @Override public void addWaiting(long time, int acquireCount) { data.addWaiting(time, acquireCount); } @Override public void addOccupiedPass(int acquireCount) { WindowWrap wrap = data.currentWindow(); wrap.value().addOccupiedPass(acquireCount); } @Override public void addSuccess(int count) { WindowWrap wrap = data.currentWindow(); wrap.value().addSuccess(count); } @Override public void addPass(int count) { WindowWrap wrap = data.currentWindow(); wrap.value().addPass(count); } @Override public void addRT(long rt) { WindowWrap wrap = data.currentWindow(); wrap.value().addRT(rt); } @Override public void debug() { data.debug(System.currentTimeMillis()); } @Override public long previousWindowBlock() { data.currentWindow(); WindowWrap wrap = data.getPreviousWindow(); if (wrap == null) { return 0; } return wrap.value().block(); } @Override public long previousWindowPass() { data.currentWindow(); WindowWrap wrap = data.getPreviousWindow(); if (wrap == null) { return 0; } return wrap.value().pass(); } public void add(MetricEvent event, long count) { data.currentWindow().value().add(event, count); } public long getCurrentCount(MetricEvent event) { return data.currentWindow().value().get(event); } /** * Get total sum for provided event in {@code intervalInSec}. * * @param event event to calculate * @return total sum for event */ public long getSum(MetricEvent event) { data.currentWindow(); long sum = 0; List buckets = data.values(); for (MetricBucket bucket : buckets) { sum += bucket.get(event); } return sum; } /** * Get average count for provided event per second. * * @param event event to calculate * @return average count per second for event */ public double getAvg(MetricEvent event) { return getSum(event) / data.getIntervalInSecond(); } @Override public long getWindowPass(long timeMillis) { MetricBucket bucket = data.getWindowValue(timeMillis); if (bucket == null) { return 0L; } return bucket.pass(); } @Override public long waiting() { return data.currentWaiting(); } @Override public double getWindowIntervalInSec() { return data.getIntervalInSecond(); } @Override public int getSampleCount() { return data.getSampleCount(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; /** * The fundamental data structure for metric statistics in a time span. * * @author jialiang.linjl * @author Eric Zhao * @see LeapArray */ public class BucketLeapArray extends LeapArray { public BucketLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); } @Override public MetricBucket newEmptyBucket(long time) { return new MetricBucket(); } @Override protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { // Update the start time and reset value. w.resetTo(startTime); w.value().reset(); return w; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; /** * @author Eric Zhao * @since 1.5.0 */ public interface DebugSupport { /** * For debug; */ void debug(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.List; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.util.function.Predicate; /** * Represents a basic structure recording invocation metrics of protected resources. * * @author jialiang.linjl * @author Eric Zhao */ public interface Metric extends DebugSupport { /** * Get total success count. * * @return success count */ long success(); /** * Get max success count. * * @return max success count */ long maxSuccess(); /** * Get total exception count. * * @return exception count */ long exception(); /** * Get total block count. * * @return block count */ long block(); /** * Get total pass count. not include {@link #occupiedPass()} * * @return pass count */ long pass(); /** * Get total response time. * * @return total RT */ long rt(); /** * Get the minimal RT. * * @return minimal RT */ long minRt(); /** * Get aggregated metric nodes of all resources. * * @return metric node list of all resources */ List details(); /** * Generate aggregated metric items that satisfies the time predicate. * * @param timePredicate time predicate * @return aggregated metric items * @since 1.7.0 */ List detailsOnCondition(Predicate timePredicate); /** * Get the raw window array. * * @return window metric array */ MetricBucket[] windows(); /** * Add current exception count. * * @param n count to add */ void addException(int n); /** * Add current block count. * * @param n count to add */ void addBlock(int n); /** * Add current completed count. * * @param n count to add */ void addSuccess(int n); /** * Add current pass count. * * @param n count to add */ void addPass(int n); /** * Add given RT to current total RT. * * @param rt RT */ void addRT(long rt); /** * Get the sliding window length in seconds. * * @return the sliding window length */ double getWindowIntervalInSec(); /** * Get sample count of the sliding window. * * @return sample count of the sliding window. */ int getSampleCount(); /** * Note: this operation will not perform refreshing, so will not generate new buckets. * * @param timeMillis valid time in ms * @return pass count of the bucket exactly associated to provided timestamp, or 0 if the timestamp is invalid * @since 1.5.0 */ long getWindowPass(long timeMillis); // Occupy-based (@since 1.5.0) /** * Add occupied pass, which represents pass requests that borrow the latter windows' token. * * @param acquireCount tokens count. * @since 1.5.0 */ void addOccupiedPass(int acquireCount); /** * Add request that occupied. * * @param futureTime future timestamp that the acquireCount should be added on. * @param acquireCount tokens count. * @since 1.5.0 */ void addWaiting(long futureTime, int acquireCount); /** * Get waiting pass account * * @return waiting pass count * @since 1.5.0 */ long waiting(); /** * Get occupied pass count. * * @return occupied pass count * @since 1.5.0 */ long occupiedPass(); // Tool methods. long previousWindowBlock(); long previousWindowPass(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric.occupy; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; /** * A kind of {@code BucketLeapArray} that only reserves for future buckets. * * @author jialiang.linjl * @since 1.5.0 */ public class FutureBucketLeapArray extends LeapArray { public FutureBucketLeapArray(int sampleCount, int intervalInMs) { // This class is the original "BorrowBucketArray". super(sampleCount, intervalInMs); } @Override public MetricBucket newEmptyBucket(long time) { return new MetricBucket(); } @Override protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { // Update the start time and reset value. w.resetTo(startTime); w.value().reset(); return w; } @Override public boolean isWindowDeprecated(long time, WindowWrap windowWrap) { // Tricky: will only calculate for future. return time >= windowWrap.windowStart(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/OccupiableBucketLeapArray.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric.occupy; import java.util.List; import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; /** * @author jialiang.linjl * @since 1.5.0 */ public class OccupiableBucketLeapArray extends LeapArray { private final FutureBucketLeapArray borrowArray; public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) { // This class is the original "CombinedBucketArray". super(sampleCount, intervalInMs); this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs); } @Override public MetricBucket newEmptyBucket(long time) { MetricBucket newBucket = new MetricBucket(); MetricBucket borrowBucket = borrowArray.getWindowValue(time); if (borrowBucket != null) { newBucket.reset(borrowBucket); } return newBucket; } @Override protected WindowWrap resetWindowTo(WindowWrap w, long time) { // Update the start time and reset value. w.resetTo(time); MetricBucket borrowBucket = borrowArray.getWindowValue(time); if (borrowBucket != null) { w.value().reset(); w.value().addPass((int)borrowBucket.pass()); } else { w.value().reset(); } return w; } @Override public long currentWaiting() { borrowArray.currentWindow(); long currentWaiting = 0; List list = borrowArray.values(); for (MetricBucket window : list) { currentWaiting += window.pass(); } return currentWaiting; } @Override public void addWaiting(long time, int acquireCount) { WindowWrap window = borrowArray.currentWindow(time); window.value().add(MetricEvent.PASS, acquireCount); } @Override public void debug(long time) { StringBuilder sb = new StringBuilder(); List> lists = listAll(); sb.append("a_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); for (WindowWrap window : lists) { sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); } sb.append("\n"); lists = borrowArray.listAll(); sb.append("b_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); for (WindowWrap window : lists) { sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); } System.out.println(sb.toString()); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemBlockException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author jialiang.linjl */ public class SystemBlockException extends BlockException { private final String resourceName; public SystemBlockException(String resourceName, String message, Throwable cause) { super(message, cause); this.resourceName = resourceName; } public SystemBlockException(String resourceName, String limitType) { super(limitType); this.resourceName = resourceName; } public String getResourceName() { return resourceName; } @Override public Throwable fillInStackTrace() { return this; } /** * Return the limit type of system rule. * * @return the limit type * @since 1.4.2 */ public String getLimitType() { return getRuleLimitApp(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import com.alibaba.csp.sentinel.slots.block.AbstractRule; /** *

          * Sentinel System Rule makes the inbound traffic and capacity meet. It takes * average RT, QPS and thread count of requests into account. And it also * provides a measurement of system's load, but only available on Linux. *

          *

          * We recommend to coordinate {@link #highestSystemLoad}, {@link #qps}, {@link #avgRt} * and {@link #maxThread} to make sure your system run in safety level. *

          *

          * To set the threshold appropriately, performance test may be needed. *

          * * @author jialiang.linjl * @author Carpenter Lee * @see SystemRuleManager */ public class SystemRule extends AbstractRule { /** * negative value means no threshold checking. */ private double highestSystemLoad = -1; /** * cpu usage, between [0, 1] */ private double highestCpuUsage = -1; private double qps = -1; private long avgRt = -1; private long maxThread = -1; public double getQps() { return qps; } /** * Set max total QPS. In a high concurrency condition, real passed QPS may be greater than max QPS set. * The real passed QPS will nearly satisfy the following formula:
          * *
          real passed QPS = QPS set + concurrent thread number
          * * @param qps max total QOS, values <= 0 are special for clearing the threshold. */ public void setQps(double qps) { this.qps = qps; } public long getMaxThread() { return maxThread; } /** * Set max PARALLEL working thread. When concurrent thread number is greater than {@code maxThread} only * maxThread will run in parallel. * * @param maxThread max parallel thread number, values <= 0 are special for clearing the threshold. */ public void setMaxThread(long maxThread) { this.maxThread = maxThread; } public long getAvgRt() { return avgRt; } /** * Set max average RT(response time) of all passed requests. * * @param avgRt max average response time, values <= 0 are special for clearing the threshold. */ public void setAvgRt(long avgRt) { this.avgRt = avgRt; } public double getHighestSystemLoad() { return highestSystemLoad; } /** *

          * Set highest load. The load is not same as Linux system load, which is not sensitive enough. * To calculate the load, both Linux system load, current global response time and global QPS will be considered, * which means that we need to coordinate with {@link #setAvgRt(long)} and {@link #setQps(double)} *

          *

          * Note that this parameter is only available on Unix like system. *

          * * @param highestSystemLoad highest system load, values <= 0 are special for clearing the threshold. * @see SystemRuleManager */ public void setHighestSystemLoad(double highestSystemLoad) { this.highestSystemLoad = highestSystemLoad; } /** * Get highest cpu usage. Cpu usage is between [0, 1] * * @return highest cpu usage */ public double getHighestCpuUsage() { return highestCpuUsage; } /** * set highest cpu usage. Cpu usage is between [0, 1] * * @param highestCpuUsage the value to set. */ public void setHighestCpuUsage(double highestCpuUsage) { this.highestCpuUsage = highestCpuUsage; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof SystemRule)) { return false; } if (!super.equals(o)) { return false; } SystemRule that = (SystemRule)o; if (Double.compare(that.highestSystemLoad, highestSystemLoad) != 0) { return false; } if (Double.compare(that.highestCpuUsage, highestCpuUsage) != 0) { return false; } if (Double.compare(that.qps, qps) != 0) { return false; } if (avgRt != that.avgRt) { return false; } return maxThread == that.maxThread; } @Override public int hashCode() { int result = super.hashCode(); long temp; temp = Double.doubleToLongBits(highestSystemLoad); result = 31 * result + (int)(temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(highestCpuUsage); result = 31 * result + (int)(temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(qps); result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + (int)(avgRt ^ (avgRt >>> 32)); result = 31 * result + (int)(maxThread ^ (maxThread >>> 32)); return result; } @Override public String toString() { return "SystemRule{" + "highestSystemLoad=" + highestSystemLoad + ", highestCpuUsage=" + highestCpuUsage + ", qps=" + qps + ", avgRt=" + avgRt + ", maxThread=" + maxThread + "}"; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; /** *

          * Sentinel System Rule makes the inbound traffic and capacity meet. It takes * average rt, qps, thread count of incoming requests into account. And it also * provides a measurement of system's load, but only available on Linux. *

          *

          * rt, qps, thread count is easy to understand. If the incoming requests' * rt,qps, thread count exceeds its threshold, the requests will be * rejected.however, we use a different method to calculate the load. *

          *

          * Consider the system as a pipeline,transitions between constraints result in * three different regions (traffic-limited, capacity-limited and danger area) * with qualitatively different behavior. When there isn’t enough request in * flight to fill the pipe, RTprop determines behavior; otherwise, the system * capacity dominates. Constraint lines intersect at inflight = Capacity × * RTprop. Since the pipe is full past this point, the inflight –capacity excess * creates a queue, which results in the linear dependence of RTT on inflight * traffic and an increase in system load.In danger area, system will stop * responding.
          * Referring to BBR algorithm to learn more. *

          *

          * Note that {@link SystemRule} only effect on inbound requests, outbound traffic * will not limit by {@link SystemRule} *

          * * @author jialiang.linjl * @author leyou */ public final class SystemRuleManager { private static volatile double highestSystemLoad = Double.MAX_VALUE; /** * cpu usage, between [0, 1] */ private static volatile double highestCpuUsage = Double.MAX_VALUE; private static volatile double qps = Double.MAX_VALUE; private static volatile long maxRt = Long.MAX_VALUE; private static volatile long maxThread = Long.MAX_VALUE; /** * mark whether the threshold are set by user. */ private static volatile boolean highestSystemLoadIsSet = false; private static volatile boolean highestCpuUsageIsSet = false; private static volatile boolean qpsIsSet = false; private static volatile boolean maxRtIsSet = false; private static volatile boolean maxThreadIsSet = false; private static AtomicBoolean checkSystemStatus = new AtomicBoolean(false); private static SystemStatusListener statusListener = null; private final static SystemPropertyListener listener = new SystemPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-system-status-record-task", true)); static { checkSystemStatus.set(false); statusListener = new SystemStatusListener(); scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS); currentProperty.addListener(listener); } /** * Listen to the {@link SentinelProperty} for {@link SystemRule}s. The property is the source * of {@link SystemRule}s. System rules can also be set by {@link #loadRules(List)} directly. * * @param property the property to listen. */ public static void register2Property(SentinelProperty> property) { synchronized (listener) { RecordLog.info("[SystemRuleManager] Registering new property to system rule manager"); currentProperty.removeListener(listener); property.addListener(listener); currentProperty = property; } } /** * Load {@link SystemRule}s, former rules will be replaced. * * @param rules new rules to load. */ public static void loadRules(List rules) { currentProperty.updateValue(rules); } /** * Get a copy of the rules. * * @return a new copy of the rules. */ public static List getRules() { List result = new ArrayList(); if (!checkSystemStatus.get()) { return result; } if (highestSystemLoadIsSet) { SystemRule loadRule = new SystemRule(); loadRule.setHighestSystemLoad(highestSystemLoad); result.add(loadRule); } if (highestCpuUsageIsSet) { SystemRule rule = new SystemRule(); rule.setHighestCpuUsage(highestCpuUsage); result.add(rule); } if (maxRtIsSet) { SystemRule rtRule = new SystemRule(); rtRule.setAvgRt(maxRt); result.add(rtRule); } if (maxThreadIsSet) { SystemRule threadRule = new SystemRule(); threadRule.setMaxThread(maxThread); result.add(threadRule); } if (qpsIsSet) { SystemRule qpsRule = new SystemRule(); qpsRule.setQps(qps); result.add(qpsRule); } return result; } public static double getInboundQpsThreshold() { return qps; } public static long getRtThreshold() { return maxRt; } public static long getMaxThreadThreshold() { return maxThread; } static class SystemPropertyListener extends SimplePropertyListener> { @Override public synchronized void configUpdate(List rules) { restoreSetting(); // systemRules = rules; if (rules != null && rules.size() >= 1) { for (SystemRule rule : rules) { loadSystemConf(rule); } } else { checkSystemStatus.set(false); } RecordLog.info(String.format("[SystemRuleManager] Current system check status: %s, " + "highestSystemLoad: %e, " + "highestCpuUsage: %e, " + "maxRt: %d, " + "maxThread: %d, " + "maxQps: %e", checkSystemStatus.get(), highestSystemLoad, highestCpuUsage, maxRt, maxThread, qps)); } protected void restoreSetting() { checkSystemStatus.set(false); // should restore changes highestSystemLoad = Double.MAX_VALUE; highestCpuUsage = Double.MAX_VALUE; maxRt = Long.MAX_VALUE; maxThread = Long.MAX_VALUE; qps = Double.MAX_VALUE; highestSystemLoadIsSet = false; highestCpuUsageIsSet = false; maxRtIsSet = false; maxThreadIsSet = false; qpsIsSet = false; } } public static Boolean getCheckSystemStatus() { return checkSystemStatus.get(); } public static double getSystemLoadThreshold() { return highestSystemLoad; } public static double getCpuUsageThreshold() { return highestCpuUsage; } public static void loadSystemConf(SystemRule rule) { boolean checkStatus = false; // Check if it's valid. if (rule.getHighestSystemLoad() >= 0) { highestSystemLoad = Math.min(highestSystemLoad, rule.getHighestSystemLoad()); highestSystemLoadIsSet = true; checkStatus = true; } if (rule.getHighestCpuUsage() >= 0) { if (rule.getHighestCpuUsage() > 1) { RecordLog.warn(String.format("[SystemRuleManager] Ignoring invalid SystemRule: " + "highestCpuUsage %.3f > 1", rule.getHighestCpuUsage())); } else { highestCpuUsage = Math.min(highestCpuUsage, rule.getHighestCpuUsage()); highestCpuUsageIsSet = true; checkStatus = true; } } if (rule.getAvgRt() >= 0) { maxRt = Math.min(maxRt, rule.getAvgRt()); maxRtIsSet = true; checkStatus = true; } if (rule.getMaxThread() >= 0) { maxThread = Math.min(maxThread, rule.getMaxThread()); maxThreadIsSet = true; checkStatus = true; } if (rule.getQps() >= 0) { qps = Math.min(qps, rule.getQps()); qpsIsSet = true; checkStatus = true; } checkSystemStatus.set(checkStatus); } /** * Apply {@link SystemRule} to the resource. Only inbound traffic will be checked. * * @param resourceWrapper the resource. * @throws BlockException when any system rule's threshold is exceeded. */ public static void checkSystem(ResourceWrapper resourceWrapper, int count) throws BlockException { if (resourceWrapper == null) { return; } // Ensure the checking switch is on. if (!checkSystemStatus.get()) { return; } // for inbound traffic only if (resourceWrapper.getEntryType() != EntryType.IN) { return; } // total qps double currentQps = Constants.ENTRY_NODE.passQps(); if (currentQps + count > qps) { throw new SystemBlockException(resourceWrapper.getName(), "qps"); } // total thread int currentThread = Constants.ENTRY_NODE.curThreadNum(); if (currentThread > maxThread) { throw new SystemBlockException(resourceWrapper.getName(), "thread"); } double rt = Constants.ENTRY_NODE.avgRt(); if (rt > maxRt) { throw new SystemBlockException(resourceWrapper.getName(), "rt"); } // load. BBR algorithm. if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) { if (!checkBbr(currentThread)) { throw new SystemBlockException(resourceWrapper.getName(), "load"); } } // cpu usage if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) { throw new SystemBlockException(resourceWrapper.getName(), "cpu"); } } private static boolean checkBbr(int currentThread) { if (currentThread > 1 && currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) { return false; } return true; } public static double getCurrentSystemAvgLoad() { return statusListener.getSystemAverageLoad(); } public static double getCurrentCpuUsage() { return statusListener.getCpuUsage(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.spi.Spi; /** * A {@link ProcessorSlot} that dedicates to {@link SystemRule} checking. * * @author jialiang.linjl * @author leyou */ @Spi(order = Constants.ORDER_SYSTEM_SLOT) public class SystemSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { SystemRuleManager.checkSystem(resourceWrapper, count); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.sun.management.OperatingSystemMXBean; /** * @author jialiang.linjl */ public class SystemStatusListener implements Runnable { volatile double currentLoad = -1; volatile double currentCpuUsage = -1; volatile String reason = StringUtil.EMPTY; volatile long processCpuTime = 0; volatile long processUpTime = 0; public double getSystemAverageLoad() { return currentLoad; } public double getCpuUsage() { return currentCpuUsage; } @Override public void run() { try { OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); currentLoad = osBean.getSystemLoadAverage(); /* * Java Doc copied from {@link OperatingSystemMXBean#getSystemCpuLoad()}:
          * Returns the "recent cpu usage" for the whole system. This value is a double in the [0.0,1.0] interval. * A value of 0.0 means that all CPUs were idle during the recent period of time observed, while a value * of 1.0 means that all CPUs were actively running 100% of the time during the recent period being * observed. All values between 0.0 and 1.0 are possible depending of the activities going on in the * system. If the system recent cpu usage is not available, the method returns a negative value. */ double systemCpuUsage = osBean.getSystemCpuLoad(); // calculate process cpu usage to support application running in container environment RuntimeMXBean runtimeBean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class); long newProcessCpuTime = osBean.getProcessCpuTime(); long newProcessUpTime = runtimeBean.getUptime(); int cpuCores = osBean.getAvailableProcessors(); long processCpuTimeDiffInMs = TimeUnit.NANOSECONDS .toMillis(newProcessCpuTime - processCpuTime); long processUpTimeDiffInMs = newProcessUpTime - processUpTime; double processCpuUsage = (double) processCpuTimeDiffInMs / processUpTimeDiffInMs / cpuCores; processCpuTime = newProcessCpuTime; processUpTime = newProcessUpTime; currentCpuUsage = Math.max(processCpuUsage, systemCpuUsage); if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) { writeSystemStatusLog(); } } catch (Throwable e) { RecordLog.warn("[SystemStatusListener] Failed to get system metrics from JMX", e); } } private void writeSystemStatusLog() { StringBuilder sb = new StringBuilder(); sb.append("Load exceeds the threshold: "); sb.append("load:").append(String.format("%.4f", currentLoad)).append("; "); sb.append("cpuUsage:").append(String.format("%.4f", currentCpuUsage)).append("; "); sb.append("qps:").append(String.format("%.4f", Constants.ENTRY_NODE.passQps())).append("; "); sb.append("rt:").append(String.format("%.4f", Constants.ENTRY_NODE.avgRt())).append("; "); sb.append("thread:").append(Constants.ENTRY_NODE.curThreadNum()).append("; "); sb.append("success:").append(String.format("%.4f", Constants.ENTRY_NODE.successQps())).append("; "); sb.append("minRt:").append(String.format("%.2f", Constants.ENTRY_NODE.minRt())).append("; "); sb.append("maxSuccess:").append(String.format("%.2f", Constants.ENTRY_NODE.maxSuccessQps())).append("; "); RecordLog.info(sb.toString()); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/Spi.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.spi; import java.lang.annotation.*; /** * Annotation for Provider class of SPI. * * @see SpiLoader * @author cdfive */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface Spi { /** * Alias name of Provider class */ String value() default ""; /** * Whether create singleton instance */ boolean isSingleton() default true; /** * Whether is the default Provider */ boolean isDefault() default false; /** * Order priority of Provider class */ int order() default 0; int ORDER_HIGHEST = Integer.MIN_VALUE; int ORDER_LOWEST = Integer.MAX_VALUE; } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiLoader.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.spi; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.io.*; import java.lang.reflect.Modifier; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; /** * A simple SPI loading facility (refactored since 1.8.1). * *

          SPI is short for Service Provider Interface.

          * *

          * Service is represented by a single type, that is, a single interface or an abstract class. * Provider is implementations of Service, that is, some classes which implement the interface or extends the abstract class. *

          * *

          * For Service type: * Must interface or abstract class. *

          * *

          * For Provider class: * Must have a zero-argument constructor so that they can be instantiated during loading. *

          * *

          * For Provider configuration file: * 1. The file contains a list of fully-qualified binary names of concrete provider classes, one per line. * 2. Space and tab characters surrounding each name, as well as blank lines, are ignored. * 3. The comment line character is #, all characters following it are ignored. *

          * * *

          {@code SpiLoader} provide common functions, such as:

          *
            *
          • Load all Provider instance unsorted/sorted list.
          • *
          • Load highest/lowest order priority instance.
          • *
          • Load first-found or default instance.
          • *
          • Load instance by alias name or provider class.
          • *
          * * @author Eric Zhao * @author cdfive * @since 1.4.0 * @see com.alibaba.csp.sentinel.spi.Spi * @see java.util.ServiceLoader */ public final class SpiLoader { // Default path for the folder of Provider configuration file private static final String SPI_FILE_PREFIX = "META-INF/services/"; // Cache the SpiLoader instances, key: classname of Service, value: SpiLoader instance private static final ConcurrentHashMap SPI_LOADER_MAP = new ConcurrentHashMap<>(); // Cache the classes of Provider private final List> classList = Collections.synchronizedList(new ArrayList>()); // Cache the sorted classes of Provider private final List> sortedClassList = Collections.synchronizedList(new ArrayList>()); /** * Cache the classes of Provider, key: aliasName, value: class of Provider. * Note: aliasName is the value of {@link Spi} when the Provider class has {@link Spi} annotation and value is not empty, * otherwise use classname of the Provider. */ private final ConcurrentHashMap> classMap = new ConcurrentHashMap<>(); // Cache the singleton instance of Provider, key: classname of Provider, value: Provider instance private final ConcurrentHashMap singletonMap = new ConcurrentHashMap<>(); // Whether this SpiLoader has been loaded, that is, loaded the Provider configuration file private final AtomicBoolean loaded = new AtomicBoolean(false); // Default provider class private Class defaultClass = null; // The Service class, must be interface or abstract class private Class service; /** * Create SpiLoader instance via Service class * Cached by className, and load from cache first * * @param service Service class * @param Service type * @return SpiLoader instance */ public static SpiLoader of(Class service) { AssertUtil.notNull(service, "SPI class cannot be null"); AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()), "SPI class[" + service.getName() + "] must be interface or abstract class"); String className = service.getName(); SpiLoader spiLoader = SPI_LOADER_MAP.get(className); if (spiLoader == null) { synchronized (SpiLoader.class) { spiLoader = SPI_LOADER_MAP.get(className); if (spiLoader == null) { SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service)); spiLoader = SPI_LOADER_MAP.get(className); } } } return spiLoader; } /** * Reset and clear all SpiLoader instances. * Package privilege, used only in test cases. */ synchronized static void resetAndClearAll() { Set> entries = SPI_LOADER_MAP.entrySet(); for (Map.Entry entry : entries) { SpiLoader spiLoader = entry.getValue(); spiLoader.resetAndClear(); } SPI_LOADER_MAP.clear(); } // Private access private SpiLoader(Class service) { this.service = service; } /** * Load all Provider instances of the specified Service * * @return Provider instances list */ public List loadInstanceList() { load(); return createInstanceList(classList); } /** * Load all Provider instances of the specified Service, sorted by order value in class's {@link Spi} annotation * * @return Sorted Provider instances list */ public List loadInstanceListSorted() { load(); return createInstanceList(sortedClassList); } /** * Load highest order priority instance, order value is defined in class's {@link Spi} annotation * * @return Provider instance of highest order priority */ public S loadHighestPriorityInstance() { load(); if (sortedClassList.size() == 0) { return null; } Class highestClass = sortedClassList.get(0); return createInstance(highestClass); } /** * Load lowest order priority instance, order value is defined in class's {@link Spi} annotation * * @return Provider instance of lowest order priority */ public S loadLowestPriorityInstance() { load(); if (sortedClassList.size() == 0) { return null; } Class lowestClass = sortedClassList.get(sortedClassList.size() - 1); return createInstance(lowestClass); } /** * Load the first-found Provider instance * * @return Provider instance of first-found specific */ public S loadFirstInstance() { load(); if (classList.size() == 0) { return null; } Class serviceClass = classList.get(0); S instance = createInstance(serviceClass); return instance; } /** * Load the first-found Provider instance,if not found, return default Provider instance * * @return Provider instance */ public S loadFirstInstanceOrDefault() { load(); for (Class clazz : classList) { if (defaultClass == null || clazz != defaultClass) { return createInstance(clazz); } } return loadDefaultInstance(); } /** * Load default Provider instance * Provider class with @Spi(isDefault = true) * * @return default Provider instance */ public S loadDefaultInstance() { load(); if (defaultClass == null) { return null; } return createInstance(defaultClass); } /** * Load instance by specific class type * * @param clazz class type * @return Provider instance */ public S loadInstance(Class clazz) { AssertUtil.notNull(clazz, "SPI class cannot be null"); if (clazz.equals(service)) { fail(clazz.getName() + " is not subtype of " + service.getName()); } load(); if (!classMap.containsValue(clazz)) { fail(clazz.getName() + " is not Provider class of " + service.getName() + ",check if it is in the SPI configuration file?"); } return createInstance(clazz); } /** * Load instance by aliasName of Provider class * * @param aliasName aliasName of Provider class * @return Provider instance */ public S loadInstance(String aliasName) { AssertUtil.notEmpty(aliasName, "aliasName cannot be empty"); load(); Class clazz = classMap.get(aliasName); if (clazz == null) { fail("no Provider class's aliasName is " + aliasName); } return createInstance(clazz); } /** * Reset and clear all fields of current SpiLoader instance and remove instance in SPI_LOADER_MAP */ synchronized void resetAndClear() { SPI_LOADER_MAP.remove(service.getName()); classList.clear(); sortedClassList.clear(); classMap.clear(); singletonMap.clear(); defaultClass = null; loaded.set(false); } /** * Load the Provider class from Provider configuration file */ public void load() { if (!loaded.compareAndSet(false, true)) { return; } String fullFileName = SPI_FILE_PREFIX + service.getName(); ClassLoader classLoader; if (SentinelConfig.shouldUseContextClassloader()) { classLoader = Thread.currentThread().getContextClassLoader(); } else { classLoader = service.getClassLoader(); } if (classLoader == null) { classLoader = ClassLoader.getSystemClassLoader(); } Enumeration urls = null; try { urls = classLoader.getResources(fullFileName); } catch (IOException e) { fail("Error locating SPI configuration file,filename=" + fullFileName + ",classloader=" + classLoader, e); } if (urls == null || !urls.hasMoreElements()) { RecordLog.warn("No SPI configuration file,filename=" + fullFileName + ",classloader=" + classLoader); return; } while (urls.hasMoreElements()) { URL url = urls.nextElement(); InputStream in = null; BufferedReader br = null; try { in = url.openStream(); br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { if (StringUtil.isBlank(line)) { // Skip blank line continue; } line = line.trim(); int commentIndex = line.indexOf("#"); if (commentIndex == 0) { // Skip comment line continue; } if (commentIndex > 0) { line = line.substring(0, commentIndex); } line = line.trim(); Class clazz = null; try { clazz = (Class) Class.forName(line, false, classLoader); } catch (ClassNotFoundException e) { fail("class " + line + " not found", e); } if (classMap.containsValue(clazz)) { RecordLog.warn("duplicate class found,className=" + clazz.getName() + ",SPI configuration file[" + url + "]"); continue; } if (!service.isAssignableFrom(clazz)) { fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file[" + url + "]"); } classList.add(clazz); Spi spi = clazz.getAnnotation(Spi.class); String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value(); if (classMap.containsKey(aliasName)) { Class existClass = classMap.get(aliasName); fail("Found repeat alias name for " + clazz.getName() + " and " + existClass.getName() + ",SPI configuration file[" + url + "]"); } classMap.put(aliasName, clazz); if (spi != null && spi.isDefault()) { if (defaultClass != null) { fail("Found more than one default Provider,className=" + clazz.getName() + ",SPI configuration file[" + url + "]"); } defaultClass = clazz; } RecordLog.info("[SpiLoader] Found SPI implementation for SPI {}, provider={}, aliasName={}" + ", isSingleton={}, isDefault={}, order={}", service.getName(), line, aliasName , spi == null ? true : spi.isSingleton() , spi == null ? false : spi.isDefault() , spi == null ? 0 : spi.order()); } } catch (IOException e) { fail("error reading SPI configuration file[" + url + "]", e); } finally { closeResources(in, br); } } sortedClassList.addAll(classList); Collections.sort(sortedClassList, new Comparator>() { @Override public int compare(Class o1, Class o2) { Spi spi1 = o1.getAnnotation(Spi.class); int order1 = spi1 == null ? 0 : spi1.order(); Spi spi2 = o2.getAnnotation(Spi.class); int order2 = spi2 == null ? 0 : spi2.order(); return Integer.compare(order1, order2); } }); } @Override public String toString() { return "com.alibaba.csp.sentinel.spi.SpiLoader[" + service.getName() + "]"; } /** * Create Provider instance list * * @param clazzList class types of Providers * @return Provider instance list */ private List createInstanceList(List> clazzList) { if (clazzList == null || clazzList.size() == 0) { return Collections.emptyList(); } List instances = new ArrayList<>(clazzList.size()); for (Class clazz : clazzList) { S instance = createInstance(clazz); instances.add(instance); } return instances; } /** * Create Provider instance * * @param clazz class type of Provider * @return Provider class */ private S createInstance(Class clazz) { Spi spi = clazz.getAnnotation(Spi.class); boolean singleton = true; if (spi != null) { singleton = spi.isSingleton(); } return createInstance(clazz, singleton); } /** * Create Provider instance * * @param clazz class type of Provider * @param singleton if instance is singleton or prototype * @return Provider instance */ private S createInstance(Class clazz, boolean singleton) { S instance = null; try { if (singleton) { instance = singletonMap.get(clazz.getName()); if (instance == null) { synchronized (this) { instance = singletonMap.get(clazz.getName()); if (instance == null) { instance = service.cast(clazz.newInstance()); singletonMap.put(clazz.getName(), instance); } } } } else { instance = service.cast(clazz.newInstance()); } } catch (Throwable e) { fail(clazz.getName() + " could not be instantiated"); } return instance; } /** * Close all resources * * @param closeables {@link Closeable} resources */ private void closeResources(Closeable... closeables) { if (closeables == null || closeables.length == 0) { return; } Exception firstException = null; for (Closeable closeable : closeables) { try { closeable.close(); } catch (Exception e) { if (firstException == null) { firstException = e; } } } if (firstException != null) { fail("error closing resources", firstException); } } /** * Throw {@link SpiLoaderException} with message * * @param msg error message */ private void fail(String msg) { RecordLog.error(msg); throw new SpiLoaderException("[" + service.getName() + "]" + msg); } /** * Throw {@link SpiLoaderException} with message and Throwable * * @param msg error message */ private void fail(String msg, Throwable e) { RecordLog.error(msg, e); throw new SpiLoaderException("[" + service.getName() + "]" + msg, e); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/SpiLoaderException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.spi; /** * Error thrown when something goes wrong while loading Provider via {@link SpiLoader}. * * @author cdfive */ public class SpiLoaderException extends RuntimeException { public SpiLoaderException() { super(); } public SpiLoaderException(String message) { super(message); } public SpiLoaderException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import com.alibaba.csp.sentinel.config.SentinelConfig; /** * @author Eric Zhao * @author leyou */ public final class AppNameUtil { private AppNameUtil() { } public static String getAppName() { return SentinelConfig.getAppName(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.util.Collection; /** * Util class for checking arguments. * * @author Eric Zhao */ public class AssertUtil { private AssertUtil() {} public static void assertNotNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } public static void assertTrue(boolean value, String message) { if (!value) { throw new IllegalArgumentException(message); } } public static void notEmpty(String string, String message) { if (StringUtil.isEmpty(string)) { throw new IllegalArgumentException(message); } } public static void assertNotEmpty(Collection collection, String message) { if (collection == null || collection.isEmpty()) { throw new IllegalArgumentException(message); } } public static void assertNotBlank(String string, String message) { if (StringUtil.isBlank(string)) { throw new IllegalArgumentException(message); } } public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } public static void isTrue(boolean value, String message) { if (!value) { throw new IllegalArgumentException(message); } } public static void assertState(boolean condition, String message) { if (!condition) { throw new IllegalStateException(message); } } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/ConfigUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; /** *

          * Util class for loading configuration from file or command arguments. *

          * * @author lianglin * @since 1.7.0 */ public final class ConfigUtil { public static final String CLASSPATH_FILE_FLAG = "classpath:"; /** *

          Load the properties from provided file.

          *

          Currently it supports reading from classpath file or local file.

          * * @param fileName valid file path * @return the retrieved properties from the file; null if the file not exist */ public static Properties loadProperties(String fileName) { if (StringUtil.isNotBlank(fileName)) { if (absolutePathStart(fileName)) { return loadPropertiesFromAbsoluteFile(fileName); } else if (fileName.startsWith(CLASSPATH_FILE_FLAG)) { return loadPropertiesFromClasspathFile(fileName); } else { return loadPropertiesFromRelativeFile(fileName); } } else { return null; } } private static Properties loadPropertiesFromAbsoluteFile(String fileName) { Properties properties = null; try { File file = new File(fileName); if (!file.exists()) { return null; } try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), getCharset()))) { properties = new Properties(); properties.load(bufferedReader); } } catch (Throwable e) { e.printStackTrace(); } return properties; } private static boolean absolutePathStart(String path) { File[] files = File.listRoots(); for (File file : files) { if (path.startsWith(file.getPath())) { return true; } } return false; } private static Properties loadPropertiesFromClasspathFile(String fileName) { fileName = fileName.substring(CLASSPATH_FILE_FLAG.length()).trim(); List list = new ArrayList<>(); try { Enumeration urls = getClassLoader().getResources(fileName); list = new ArrayList<>(); while (urls.hasMoreElements()) { list.add(urls.nextElement()); } } catch (Throwable e) { e.printStackTrace(); } if (list.isEmpty()) { return null; } Properties properties = new Properties(); for (URL url : list) { try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) { Properties p = new Properties(); p.load(bufferedReader); properties.putAll(p); } catch (Throwable e) { e.printStackTrace(); } } return properties; } private static Properties loadPropertiesFromRelativeFile(String fileName) { String userDir = System.getProperty("user.dir"); String realFilePath = addSeparator(userDir) + fileName; return loadPropertiesFromAbsoluteFile(realFilePath); } private static ClassLoader getClassLoader() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { classLoader = ConfigUtil.class.getClassLoader(); } return classLoader; } private static Charset getCharset() { // avoid static loop dependencies: SentinelConfig -> SentinelConfigLoader -> ConfigUtil -> SentinelConfig // so not use SentinelConfig.charset() return Charset.forName(System.getProperty("csp.sentinel.charset", StandardCharsets.UTF_8.name())); } public static String addSeparator(String dir) { if (!dir.endsWith(File.separator)) { dir += File.separator; } return dir; } private ConfigUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/HostNameUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import com.alibaba.csp.sentinel.log.RecordLog; /** * Get host name and ip of the host. * * @author leyou */ public final class HostNameUtil { private static String ip; private static String hostName; static { try { // Init the host information. resolveHost(); } catch (Exception e) { RecordLog.info("Failed to get local host", e); } } private static void resolveHost() throws Exception { InetAddress addr = InetAddress.getLocalHost(); hostName = addr.getHostName(); ip = addr.getHostAddress(); if (addr.isLoopbackAddress()) { // find the first IPv4 Address that not loopback Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface in = interfaces.nextElement(); Enumeration addrs = in.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress address = addrs.nextElement(); if (!address.isLoopbackAddress() && address instanceof Inet4Address) { ip = address.getHostAddress(); } } } } } public static String getIp() { return ip; } public static String getHostName() { return hostName; } public static String getConfigString() { return "{\n" + "\t\"machine\": \"" + hostName + "\",\n" + "\t\"ip\": \"" + ip + "\"\n" + "}"; } private HostNameUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/IdUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; /** * @author qinan.qn */ public final class IdUtil { public static String truncate(String id) { IdLexer lexer = new IdLexer(id); StringBuilder sb = new StringBuilder(); String r; String temp = ""; while ((r = lexer.nextToken()) != null) { if ("(".equals(r) || ")".equals(r) || ",".equals(r)) { sb.append(temp).append(r); temp = ""; } else if (!".".equals(r)) { temp = r; } } return sb.toString(); } private static class IdLexer { private String id; private int idx = 0; IdLexer(String id) { this.id = id; } String nextToken() { int oldIdx = idx; String result = null; while (idx != id.length()) { char curChar = id.charAt(idx); if (curChar == '.' || curChar == '(' || curChar == ')' || curChar == ',') { if (idx == oldIdx) { result = String.valueOf(curChar); ++idx; break; } else { result = id.substring(oldIdx, idx); break; } } ++idx; } return result; } } private IdUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/MethodUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /*** * Util class for processing {@link Method}. * * @author youji.zj */ public final class MethodUtil { private static final Map methodNameMap = new ConcurrentHashMap(); private static final Object LOCK = new Object(); /** * Parse and resolve the method name, then cache to the map. * * @param method method instance * @return resolved method name */ public static String resolveMethodName(Method method) { if (method == null) { throw new IllegalArgumentException("Null method"); } String methodName = methodNameMap.get(method); if (methodName == null) { synchronized (LOCK) { methodName = methodNameMap.get(method); if (methodName == null) { StringBuilder sb = new StringBuilder(); String className = method.getDeclaringClass().getName(); String name = method.getName(); Class[] params = method.getParameterTypes(); sb.append(className).append(":").append(name); sb.append("("); int paramPos = 0; for (Class clazz : params) { sb.append(clazz.getCanonicalName()); if (++paramPos < params.length) { sb.append(","); } } sb.append(")"); methodName = sb.toString(); methodNameMap.put(method, methodName); } } } return methodName; } /** * For test. */ static void clearMethodMap() { methodNameMap.clear(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/PidUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.lang.management.ManagementFactory; /** * Util class providing pid of current process. */ public final class PidUtil { /** * Resolve and get current process ID. * * @return current process ID */ public static int getPid() { // Note: this will trigger local host resolve, which might be slow. String name = ManagementFactory.getRuntimeMXBean().getName(); return Integer.parseInt(name.split("@")[0]); } private PidUtil() {} } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/StringUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; /*** * Util class providing operations on {@link String}. * * @author youji.zj */ public final class StringUtil { public static final String EMPTY = ""; public static boolean equalsIgnoreCase(final CharSequence str1, final CharSequence str2) { if (str1 == null || str2 == null) { return str1 == str2; } else if (str1 == str2) { return true; } else if (str1.length() != str2.length()) { return false; } else { return regionMatches(str1, true, 0, str2, 0, str1.length()); } } public static boolean equals(String str1, String str2) { return str1 == null ? str2 == null : str1.equals(str2); } 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)))) { return false; } } return true; } public static boolean isNotBlank(String str) { return !isBlank(str); } public static boolean isEmpty(String str) { return str == null || str.length() == 0; } public static boolean isNotEmpty(String str) { return !isEmpty(str); } public static String trimToEmpty(String str) { return str == null ? EMPTY : str.trim(); } public static String trim(String str) { return str == null ? null : str.trim(); } private static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, final int length) { if (cs instanceof String && substring instanceof String) { return ((String)cs).regionMatches(ignoreCase, thisStart, (String)substring, start, length); } int index1 = thisStart; int index2 = start; int tmpLen = length; // Extract these first so we detect NPEs the same as the java.lang.String version final int srcLen = cs.length() - thisStart; final int otherLen = substring.length() - start; // Check for invalid parameters if (thisStart < 0 || start < 0 || length < 0) { return false; } // Check that the regions are long enough if (srcLen < length || otherLen < length) { return false; } while (tmpLen-- > 0) { final char c1 = cs.charAt(index1++); final char c2 = substring.charAt(index2++); if (c1 == c2) { continue; } if (!ignoreCase) { return false; } // The same check as in String.regionMatches(): if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { return false; } } return true; } public static String capitalize(String str) { return changeFirstCharacterCase(str, true); } private static String changeFirstCharacterCase(String str, boolean capitalize) { if (str == null || str.length() == 0) { return str; } StringBuilder buf = new StringBuilder(str.length()); if (capitalize) { buf.append(Character.toUpperCase(str.charAt(0))); } else { buf.append(Character.toLowerCase(str.charAt(0))); } buf.append(str.substring(1)); return buf.toString(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/TimeUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.util.function.Tuple2; /** *

          Provides millisecond-level time of OS.

          *

          * Here we should see that not all the time TimeUtil should * keep looping 1_000 times every second (Actually about 800/s due to some losses). *

           * * In idle conditions it just acts as System.currentTimeMillis();
           * * In busy conditions (significantly more than 1_000/s) it keeps loop to reduce costs.
           * 
          * For detail design and proposals please goto * https://github.com/alibaba/Sentinel/issues/1702 * * @author qinan.qn * @author jason */ public final class TimeUtil implements Runnable { private static final long CHECK_INTERVAL = 3000; private static final long HITS_LOWER_BOUNDARY = 800; private static final long HITS_UPPER_BOUNDARY = 1200; public static enum STATE { IDLE, PREPARE, RUNNING; } private static class Statistic { private final LongAdder writes = new LongAdder(); private final LongAdder reads = new LongAdder(); public LongAdder getWrites() { return writes; } public LongAdder getReads() { return reads; } } private static TimeUtil INSTANCE; private volatile long currentTimeMillis; private volatile STATE state = STATE.IDLE; private LeapArray statistics; /** * thread private variables */ private long lastCheck = 0; static { INSTANCE = new TimeUtil(); } public TimeUtil() { this.statistics = new LeapArray(3, 3000) { @Override public Statistic newEmptyBucket(long timeMillis) { return new Statistic(); } @Override protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { Statistic val = windowWrap.value(); val.getReads().reset(); val.getWrites().reset(); windowWrap.resetTo(startTime); return windowWrap; } }; this.currentTimeMillis = System.currentTimeMillis(); this.lastCheck = this.currentTimeMillis; Thread daemon = new Thread(this); daemon.setDaemon(true); daemon.setName("sentinel-time-tick-thread"); daemon.start(); } @Override public void run() { while (true) { // Mechanism optimized since 1.8.2 this.check(); if (this.state == STATE.RUNNING) { this.currentTimeMillis = System.currentTimeMillis(); this.statistics.currentWindow(this.currentTimeMillis).value().getWrites().increment(); try { TimeUnit.MILLISECONDS.sleep(1); } catch (Throwable e) { } continue; } if (this.state == STATE.IDLE) { try { TimeUnit.MILLISECONDS.sleep(300); } catch (Throwable e) { } continue; } if (this.state == STATE.PREPARE) { RecordLog.debug("TimeUtil switches to RUNNING"); this.currentTimeMillis = System.currentTimeMillis(); this.state = STATE.RUNNING; continue; } } } /** * Current running state * * @return */ public STATE getState() { return state; } /** * Current qps statistics (including reads and writes request) * excluding current working time window for accurate result. * * @param now * @return */ public Tuple2 currentQps(long now) { List> list = this.statistics.listAll(); long reads = 0; long writes = 0; int cnt = 0; for (WindowWrap windowWrap : list) { if (windowWrap.isTimeInWindow(now)) { continue; } cnt++; reads += windowWrap.value().getReads().longValue(); writes += windowWrap.value().getWrites().longValue(); } if (cnt < 1) { return new Tuple2(0L, 0L); } return new Tuple2(reads / cnt, writes / cnt); } /** * Check and operate the state if necessary. * ATTENTION: It's called in daemon thread. */ private void check() { long now = currentTime(true); // every period if (now - this.lastCheck < CHECK_INTERVAL) { return; } this.lastCheck = now; Tuple2 qps = currentQps(now); if (this.state == STATE.IDLE && qps.r1 > HITS_UPPER_BOUNDARY) { RecordLog.info("TimeUtil switches to PREPARE for better performance, reads={}/s, writes={}/s", qps.r1, qps.r2); this.state = STATE.PREPARE; } else if (this.state == STATE.RUNNING && qps.r1 < HITS_LOWER_BOUNDARY) { RecordLog.info("TimeUtil switches to IDLE due to not enough load, reads={}/s, writes={}/s", qps.r1, qps.r2); this.state = STATE.IDLE; } } private long currentTime(boolean innerCall) { long now = this.currentTimeMillis; Statistic val = this.statistics.currentWindow(now).value(); if (!innerCall) { val.getReads().increment(); } if (this.state == STATE.IDLE || this.state == STATE.PREPARE) { now = System.currentTimeMillis(); this.currentTimeMillis = now; if (!innerCall) { val.getWrites().increment(); } } return now; } /** * Current timestamp in milliseconds. * * @return */ public long getTime() { return this.currentTime(false); } public static TimeUtil instance() { return INSTANCE; } public static long currentTimeMillis() { return INSTANCE.getTime(); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/VersionUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import com.alibaba.csp.sentinel.log.RecordLog; /** * Get version of Sentinel from {@code MANIFEST.MF} file. * * @author jason * @since 0.2.1 */ public final class VersionUtil { public static String getVersion(String defaultVersion) { try { String version = VersionUtil.class.getPackage().getImplementationVersion(); return StringUtil.isBlank(version) ? defaultVersion : version; } catch (Throwable e) { RecordLog.warn("Using default version, ignore exception", e); return defaultVersion; } } private VersionUtil() {} private static int parseInt(String str) { if (str == null || str.length() < 1) { return 0; } int num = 0; for (int i = 0; i < str.length(); i ++) { char ch = str.charAt(i); if (ch < '0' || ch > '9') { break; } num = num * 10 + (ch - '0'); } return num; } /** * Convert version in string like x.y.z or x.y.z.b into number
          * Each segment has one byte space(unsigned)
          * eg.
          *
               * 1.2.3.4 => 01 02 03 04
               * 1.2.3   => 01 02 03 00
               * 1.2     => 01 02 00 00
               * 1       => 01 00 00 00
               * 
          * * @return */ public static int fromVersionString(String verStr) { if (verStr == null || verStr.length() < 1) { return 0; } int[] versions = new int[] {0, 0, 0, 0}; int index = 0; String segment; int cur = 0; int pos; do { if (index >= versions.length) { // More dots than "x.y.z.b" contains return 0; } pos = verStr.indexOf('.', cur); if (pos == -1) { segment = verStr.substring(cur); } else if (cur < pos) { segment = verStr.substring(cur, pos); } else { // Illegal format return 0; } versions[index] = parseInt(segment); if (versions[index] < 0 || versions[index] > 255) { // Out of range [0, 255] return 0; } cur = pos + 1; index ++; } while (pos > 0); return ((versions[0] & 0xff) << 24) | ((versions[1] & 0xff) << 16) | ((versions[2] & 0xff) << 8) | (versions[3] & 0xff); } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/BiConsumer.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; /** * BiConsumer interface from JDK 8. */ public interface BiConsumer { void accept(T t, U u); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Consumer.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; /** * Consumer interface from JDK 8. */ public interface Consumer { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Function.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; /** * Function functional interface from JDK 8. */ public interface Function { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t); /** * Returns a function that always returns its input argument. * * @param the type of the input and output objects to the function * @return a function that always returns its input argument */ static Function identity() { return t -> t; } } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Predicate.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; /** * Predicate functional interface from JDK 8. */ public interface Predicate { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; /** * Supplier functional interface from JDK 8. */ public interface Supplier { /** * Gets a result. * * @return a result */ T get(); } ================================================ FILE: sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Tuple2.java ================================================ package com.alibaba.csp.sentinel.util.function; import java.util.Objects; /** * A tuple of 2 elements. */ public class Tuple2 { public final R1 r1; public final R2 r2; public Tuple2(R1 r1, R2 r2) { this.r1 = r1; this.r2 = r2; } /** * Factory method for creating a Tuple. * * @return new Tuple */ public static Tuple2 of(C1 c1, C2 c2) { return new Tuple2(c1, c2); } /** * Swaps the element of this Tuple. * * @return a new Tuple where the first element is the second element of this Tuple and the second element is the first element of this Tuple. */ public Tuple2 swap() { return new Tuple2(this.r2, this.r1); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Tuple2)) { return false; } Tuple2 that = (Tuple2) o; return Objects.equals(this.r1, that.r1) && Objects.equals(this.r2, that.r2); } @Override public int hashCode() { int result = r1 != null ? r1.hashCode() : 0; result = 31 * result + (r2 != null ? r2.hashCode() : 0); return result; } @Override public String toString() { return "Tuple2{" + "r1=" + r1 + ", r2=" + r2 + '}'; } } ================================================ FILE: sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit ================================================ FILE: sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot ================================================ # Sentinel default ProcessorSlots com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot com.alibaba.csp.sentinel.slots.logger.LogSlot com.alibaba.csp.sentinel.slots.statistic.StatisticSlot com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot com.alibaba.csp.sentinel.slots.system.SystemSlot com.alibaba.csp.sentinel.slots.block.flow.FlowSlot com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot ================================================ FILE: sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder ================================================ # Default slot chain builder com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryIntegrationTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.junit.Assert.fail; /** * Integration test for asynchronous entry, including common scenarios. * * @author Eric Zhao */ public class AsyncEntryIntegrationTest { @Before public void clearContext() { ContextTestUtil.cleanUpContext(); } private final ExecutorService pool = Executors.newFixedThreadPool(10); private void anotherAsync() { try { final AsyncEntry entry = SphU.asyncEntry("test-another-async"); runAsync(new Runnable() { @Override public void run() { ContextUtil.runOnContext(entry.getAsyncContext(), new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(500); anotherSyncInAsync(); System.out.println("Async result: 666"); } catch (InterruptedException e) { // Ignore. } finally { entry.exit(); } } }); } }); } catch (BlockException ex) { ex.printStackTrace(); } } private void fetchSync() { Entry entry = null; try { entry = SphU.entry("test-sync"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } private void fetchSyncInAsync() { Entry entry = null; try { entry = SphU.entry("test-sync-in-async"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } public void anotherSyncInAsync() { Entry entry = null; try { entry = SphU.entry("test-another-in-async"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } private void doAsyncThenSync() { try { // First we call an asynchronous resource. final AsyncEntry entry = SphU.asyncEntry("test-async"); this.invoke("abc", new Consumer() { @Override public void accept(final String resp) { // The thread is different from original caller thread for async entry. // So we need to wrap in the async context so that nested sync invocation entry // can be linked to the parent asynchronous entry. ContextUtil.runOnContext(entry.getAsyncContext(), new Runnable() { @Override public void run() { try { // In the callback, we do another async invocation under the async context. anotherAsync(); System.out.println(resp); // Then we do a sync entry under current async context. fetchSyncInAsync(); } finally { // Exit the async entry. entry.exit(); } } }); } }); // Then we call a sync resource. fetchSync(); } catch (BlockException ex) { // Request blocked, handle the exception. ex.printStackTrace(); } } @Test public void testAsyncEntryUnderSyncEntry() throws Exception { // Expected invocation chain: // EntranceNode: machine-root // -EntranceNode: async-context // --test-top // ---test-async // ----test-sync-in-async // ----test-another-async // -----test-another-in-async // ---test-sync ContextUtil.enter(contextName, origin); Entry entry = null; try { entry = SphU.entry("test-top"); doAsyncThenSync(); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } // we keep the original timeout of 15 seconds although the test should // complete in less than 3 seconds await().timeout(15, TimeUnit.SECONDS) .until(new Callable() { @Override public DefaultNode call() throws Exception { return queryInvocationTree(false); } }, CoreMatchers.notNullValue()); queryInvocationTree(true); } private DefaultNode queryInvocationTree(boolean check) { DefaultNode root = Constants.ROOT; DefaultNode entranceNode = shouldHasChildFor(root, contextName, check); DefaultNode testTopNode = shouldHasChildFor(entranceNode, "test-top", check); DefaultNode testAsyncNode = shouldHasChildFor(testTopNode, "test-async", check); shouldHasChildFor(testTopNode, "test-sync", check); shouldHasChildFor(testAsyncNode, "test-sync-in-async", check); DefaultNode anotherAsyncInAsyncNode = shouldHasChildFor(testAsyncNode, "test-another-async", check); return shouldHasChildFor(anotherAsyncInAsyncNode, "test-another-in-async", check); } private DefaultNode shouldHasChildFor(DefaultNode root, String resourceName, boolean check) { if (root == null) { if (check) { fail("Root node should not be empty"); } else { return null; } } Set nodeSet = root.getChildList(); if (nodeSet == null || nodeSet.isEmpty()) { if (check) { fail("Child nodes should not be empty: " + root.getId().getName()); } else { return null; } } for (Node node : nodeSet) { if (node instanceof DefaultNode) { DefaultNode dn = (DefaultNode) node; if (dn.getId().getName().equals(resourceName)) { return dn; } } } if (check) { fail(String.format("The given node <%s> does not have child for resource <%s>", root.getId().getName(), resourceName)); } return null; } @After public void shutdown() { pool.shutdownNow(); ContextTestUtil.cleanUpContext(); } private void runAsync(Runnable f) { // In Java 8, we can use CompletableFuture.runAsync(f) instead. pool.submit(f); } private void invoke(final String arg, final Consumer handler) { runAsync(new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(1000); String resp = arg + ": " + System.currentTimeMillis(); handler.accept(resp); } catch (Exception ex) { ex.printStackTrace(); } } }); } private interface Consumer { void accept(T t); } private final String contextName = "async-context"; private final String origin = "originA"; } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import org.junit.After; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link AsyncEntry}. * * @author Eric Zhao * @since 0.2.0 */ public class AsyncEntryTest { @Test public void testCleanCurrentEntryInLocal() { final String contextName = "abc"; try { ContextUtil.enter(contextName); Context curContext = ContextUtil.getContext(); Entry previousEntry = new CtEntry(new StringResourceWrapper("entry-sync", EntryType.IN), null, curContext); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testCleanCurrentEntryInLocal", EntryType.OUT), null, curContext); assertSame(entry, curContext.getCurEntry()); entry.cleanCurrentEntryInLocal(); assertNotSame(entry, curContext.getCurEntry()); assertSame(previousEntry, curContext.getCurEntry()); } finally { ContextTestUtil.cleanUpContext(); } } @Test(expected = IllegalStateException.class) public void testCleanCurrentEntryInLocalError() { final String contextName = "abc"; try { ContextUtil.enter(contextName); Context curContext = ContextUtil.getContext(); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testCleanCurrentEntryInLocal", EntryType.OUT), null, curContext); entry.cleanCurrentEntryInLocal(); entry.cleanCurrentEntryInLocal(); } finally { ContextTestUtil.cleanUpContext(); } } @Test public void testInitAndGetAsyncContext() { final String contextName = "abc"; final String origin = "xxx"; try { ContextUtil.enter(contextName, origin); Context curContext = ContextUtil.getContext(); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testInitAndGetAsyncContext", EntryType.OUT), null, curContext); assertNull(entry.getAsyncContext()); entry.initAsyncContext(); Context asyncContext = entry.getAsyncContext(); assertNotNull(asyncContext); assertEquals(contextName, asyncContext.getName()); assertEquals(origin, asyncContext.getOrigin()); assertSame(curContext.getEntranceNode(), asyncContext.getEntranceNode()); assertSame(entry, asyncContext.getCurEntry()); assertTrue(asyncContext.isAsync()); } finally { ContextTestUtil.cleanUpContext(); } } @Test public void testDuplicateInitAsyncContext() { Context context = new Context(null, "abc"); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testDuplicateInitAsyncContext", EntryType.OUT), null, context); entry.initAsyncContext(); Context asyncContext = entry.getAsyncContext(); // Duplicate init. entry.initAsyncContext(); assertSame(asyncContext, entry.getAsyncContext()); } @After public void tearDown() { ContextTestUtil.cleanUpContext(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConfigPropertyHelper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Properties; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.util.AppNameUtil; /** * Helper class for executing a task within a config context via properties file. * * @author Eric Zhao */ public final class ConfigPropertyHelper { public static void setAppNameProperty(String appName) { System.setProperty(SentinelConfig.APP_NAME_PROP_KEY, appName); } public static void clearAppNameProperty() { System.clearProperty(SentinelConfig.APP_NAME_PROP_KEY); } public static void runWithConfig(Properties prop, String appName, Task task) throws Exception { if (prop == null || appName == null || "".equals(appName)) { throw new IllegalArgumentException("Prop and appName cannot be empty"); } // Set application name property. setAppNameProperty(appName); // Save the config. String path = LogBase.getLogBaseDir() + appName + ".properties"; File file = new File(path); if (!file.exists()) { file.createNewFile(); } OutputStream outputStream = new FileOutputStream(file); prop.store(outputStream,""); outputStream.close(); // Run the procedure. task.run(); // Clean-up. file.delete(); // Clear application name property. clearAppNameProperty(); } public interface Task { void run() throws Exception; } private ConfigPropertyHelper() {} } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtEntryTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Eric Zhao */ public class CtEntryTest { @Test public void testExitNotMatchCurEntry() { String contextName = "context-rpc"; ContextUtil.enter(contextName); Context context = ContextUtil.getContext(); CtEntry entry1 = null; CtEntry entry2 = null; try { entry1 = new CtEntry(new StringResourceWrapper("res1", EntryType.IN), null, ContextUtil.getContext()); assertSame(entry1, context.getCurEntry()); entry2 = new CtEntry(new StringResourceWrapper("res2", EntryType.IN), null, ContextUtil.getContext()); assertSame(entry2, context.getCurEntry()); // Forget to exit for entry 2... // Directly exit for entry 1, then boom... entry1.exit(); } catch (ErrorEntryFreeException ex) { assertNotNull(entry1); assertNotNull(entry2); assertNull(entry1.context); assertNull(entry2.context); assertNull(context.getCurEntry()); return; } finally { ContextUtil.exit(); } fail("Mismatch entry-exit should throw an ErrorEntryFreeException"); } private Context getFakeDefaultContext() { return new Context(null, Constants.CONTEXT_DEFAULT_NAME); } @Test public void testExitLastEntryWithDefaultContext() { final Context defaultContext = getFakeDefaultContext(); ContextUtil.runOnContext(defaultContext, new Runnable() { @Override public void run() { CtEntry entry = new CtEntry(new StringResourceWrapper("res", EntryType.IN), null, ContextUtil.getContext()); assertSame(entry, defaultContext.getCurEntry()); assertSame(defaultContext, ContextUtil.getContext()); entry.exit(); assertNull(defaultContext.getCurEntry()); // Default context will be automatically exited. assertNull(ContextUtil.getContext()); } }); } @Test public void testExitTwoLastEntriesWithCustomContext() { String contextName = "context-rpc"; ContextUtil.enter(contextName); Context context = ContextUtil.getContext(); try { CtEntry entry1 = new CtEntry(new StringResourceWrapper("resA", EntryType.IN), null, context); entry1.exit(); assertEquals(context, ContextUtil.getContext()); CtEntry entry2 = new CtEntry(new StringResourceWrapper("resB", EntryType.IN), null, context); entry2.exit(); assertEquals(context, ContextUtil.getContext()); } finally { ContextUtil.exit(); assertNull(ContextUtil.getContext()); } } @Test public void testEntryAndExitWithNullContext() { Context context = new NullContext(); CtEntry entry = new CtEntry(new StringResourceWrapper("testEntryAndExitWithNullContext", EntryType.IN), null, context); assertNull(context.getCurEntry()); entry.exit(); assertNull(context.getCurEntry()); // Won't true exit, so the context won't be cleared. assertEquals(context, entry.context); } @Test public void testGetLastNode() { Context context = new NullContext(); CtEntry entry = new CtEntry(new StringResourceWrapper("testGetLastNode", EntryType.IN), null, context); assertNull(entry.parent); assertNull(entry.getLastNode()); Entry parentEntry = mock(Entry.class); Node node = mock(Node.class); when(parentEntry.getCurNode()).thenReturn(node); entry.parent = parentEntry; assertSame(node, entry.getLastNode()); } @Before public void setUp() throws Exception { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); } @After public void tearDown() throws Exception { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.SlotChainProvider; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Test cases for Sentinel internal {@link CtSph}. * * @author Eric Zhao */ public class CtSphTest { private final CtSph ctSph = new CtSph(); private void testCustomContextEntryWithFullContextSize(String resourceName, boolean async) { fillFullContext(); ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); String contextName = "custom-context-" + System.currentTimeMillis(); ContextUtil.enter(contextName, "9527"); // Prepare a slot that "should not pass". If entered the slot, exception will be thrown. addShouldNotPassSlotFor(resourceWrapper); Entry entry = null; try { if (async) { entry = ctSph.asyncEntry(resourceName, resourceWrapper.getEntryType(), 1); } else { entry = ctSph.entry(resourceWrapper, 1); } } catch (BlockException ex) { fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } } @Test public void testCustomContextSyncEntryWithFullContextSize() { String resourceName = "testCustomContextSyncEntryWithFullContextSize"; testCustomContextEntryWithFullContextSize(resourceName, false); } @Test public void testCustomContextAsyncEntryWithFullContextSize() { String resourceName = "testCustomContextAsyncEntryWithFullContextSize"; testCustomContextEntryWithFullContextSize(resourceName, true); } private void testDefaultContextEntryWithFullContextSize(String resourceName, boolean async) { fillFullContext(); ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); // Prepare a slot that "should pass". ShouldPassSlot slot = addShouldPassSlotFor(resourceWrapper); assertFalse(slot.entered || slot.exited); Entry entry = null; try { if (!async) { entry = ctSph.entry(resourceWrapper, 1); } else { entry = ctSph.asyncEntry(resourceName, resourceWrapper.getEntryType(), 1); Context asyncContext = ((AsyncEntry)entry).getAsyncContext(); assertTrue(ContextUtil.isDefaultContext(asyncContext)); assertTrue(asyncContext.isAsync()); } assertTrue(ContextUtil.isDefaultContext(ContextUtil.getContext())); assertTrue(slot.entered); } catch (BlockException ex) { fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); } finally { if (entry != null) { entry.exit(); assertTrue(slot.exited); } } } @Test public void testDefaultContextSyncEntryWithFullContextSize() { String resourceName = "testDefaultContextSyncEntryWithFullContextSize"; testDefaultContextEntryWithFullContextSize(resourceName, false); } @Test public void testDefaultContextAsyncEntryWithFullContextSize() { String resourceName = "testDefaultContextAsyncEntryWithFullContextSize"; testDefaultContextEntryWithFullContextSize(resourceName, true); } @Test public void testEntryAndAsyncEntryWhenSwitchOff() { // Turn off the switch. Constants.ON = false; String resourceNameA = "resSync"; String resourceNameB = "resAsync"; ResourceWrapper resourceWrapperA = new StringResourceWrapper(resourceNameA, EntryType.IN); ResourceWrapper resourceWrapperB = new StringResourceWrapper(resourceNameB, EntryType.IN); // Prepare a slot that "should not pass". If entered the slot, exception will be thrown. addShouldNotPassSlotFor(resourceWrapperA); addShouldNotPassSlotFor(resourceWrapperB); Entry entry = null; AsyncEntry asyncEntry = null; try { entry = ctSph.entry(resourceWrapperA, 1); asyncEntry = ctSph.asyncEntry(resourceNameB, resourceWrapperB.getEntryType(), 1); } catch (BlockException ex) { fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); } finally { if (asyncEntry != null) { asyncEntry.exit(); } if (entry != null) { entry.exit(); } Constants.ON = true; } } @Test public void testAsyncEntryNormalPass() { String resourceName = "testAsyncEntryNormalPass"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); AsyncEntry entry = null; // Prepare a slot that "should pass". ShouldPassSlot slot = addShouldPassSlotFor(resourceWrapper); assertFalse(slot.entered || slot.exited); ContextUtil.enter("abc"); Entry previousEntry = ContextUtil.getContext().getCurEntry(); try { entry = ctSph.asyncEntry(resourceName, EntryType.IN, 1); assertTrue(slot.entered); assertFalse(slot.exited); Context asyncContext = entry.getAsyncContext(); assertNotNull(asyncContext); assertSame(entry, asyncContext.getCurEntry()); assertNotSame("The async entry should not be added to current context", entry, ContextUtil.getContext().getCurEntry()); assertSame(previousEntry, ContextUtil.getContext().getCurEntry()); } catch (BlockException ex) { fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); } finally { if (entry != null) { Context asyncContext = entry.getAsyncContext(); entry.exit(); assertTrue(slot.exited); assertNull(entry.getAsyncContext()); assertSame(previousEntry, asyncContext.getCurEntry()); } ContextUtil.exit(); } } @Test public void testAsyncEntryNestedInSyncEntryNormalBlocked() { String previousResourceName = "fff"; String resourceName = "testAsyncEntryNestedInSyncEntryNormalBlocked"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); // Prepare a slot that "must block". MustBlockSlot slot = addMustBlockSlot(resourceWrapper); assertFalse(slot.exited); // Previous entry should pass. addShouldPassSlotFor(new StringResourceWrapper(previousResourceName, EntryType.IN)); ContextUtil.enter("bcd-" + System.currentTimeMillis()); AsyncEntry entry = null; Entry syncEntry = null; Entry previousEntry = null; try { // First enter a sync resource. syncEntry = ctSph.entry(previousResourceName, EntryType.IN, 1); // Record current entry (previous for next). previousEntry = ContextUtil.getContext().getCurEntry(); // Then enter an async resource. entry = ctSph.asyncEntry(resourceName, EntryType.IN, 1); // Should not pass here. } catch (BlockException ex) { assertNotNull(previousEntry); assertNull(entry); assertTrue(slot.exited); assertSame(previousEntry, ContextUtil.getContext().getCurEntry()); return; } finally { assertNull(entry); assertNotNull(syncEntry); syncEntry.exit(); ContextUtil.exit(); } fail("This async entry is expected to be blocked"); } private void testEntryAmountExceeded(boolean async) { fillFullResources(); Entry entry = null; try { if (!async) { entry = ctSph.entry("testSync", EntryType.IN, 1); } else { entry = ctSph.asyncEntry("testSync", EntryType.IN, 1); } assertNull(((CtEntry)entry).chain); if (!async) { assertSame(entry, ContextUtil.getContext().getCurEntry()); } else { Context asyncContext = ((AsyncEntry)entry).getAsyncContext(); assertNotNull(asyncContext); assertSame(entry, asyncContext.getCurEntry()); } } catch (BlockException ex) { fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); } finally { if (entry != null) { entry.exit(); } } } @Test public void testEntryAmountExceededForSyncEntry() { testEntryAmountExceeded(false); } @Test public void testEntryAmountExceededForAsyncEntry() { testEntryAmountExceeded(true); } @Test public void testLookUpSlotChain() { ResourceWrapper r1 = new StringResourceWrapper("firstRes", EntryType.IN); assertFalse(CtSph.getChainMap().containsKey(r1)); ProcessorSlot chainR1 = ctSph.lookProcessChain(r1); assertNotNull("The slot chain for r1 should be created", chainR1); assertSame("Should return the cached slot chain once it has been created", chainR1, ctSph.lookProcessChain(r1)); fillFullResources(); ResourceWrapper r2 = new StringResourceWrapper("secondRes", EntryType.IN); assertFalse(CtSph.getChainMap().containsKey(r2)); assertNull("The slot chain for r2 should not be created because amount exceeded", ctSph.lookProcessChain(r2)); assertNull(ctSph.lookProcessChain(r2)); } private void fillFullContext() { for (int i = 0; i < Constants.MAX_CONTEXT_NAME_SIZE; i++) { ContextUtil.enter("test-context-" + i); ContextUtil.exit(); } } private void fillFullResources() { for (int i = 0; i < Constants.MAX_SLOT_CHAIN_SIZE; i++) { ResourceWrapper resourceWrapper = new StringResourceWrapper("test-resource-" + i, EntryType.IN); CtSph.getChainMap().put(resourceWrapper, SlotChainProvider.newSlotChain()); } } private void addShouldNotPassSlotFor(ResourceWrapper resourceWrapper) { ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); slotChain.addLast(new ShouldNotPassSlot()); CtSph.getChainMap().put(resourceWrapper, slotChain); } private ShouldPassSlot addShouldPassSlotFor(ResourceWrapper resourceWrapper) { ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); ShouldPassSlot shouldPassSlot = new ShouldPassSlot(); slotChain.addLast(shouldPassSlot); CtSph.getChainMap().put(resourceWrapper, slotChain); return shouldPassSlot; } private MustBlockSlot addMustBlockSlot(ResourceWrapper resourceWrapper) { ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); MustBlockSlot mustBlockSlot = new MustBlockSlot(); slotChain.addLast(mustBlockSlot); CtSph.getChainMap().put(resourceWrapper, slotChain); return mustBlockSlot; } private class ShouldNotPassSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, boolean prioritized, Object... args) { throw new IllegalStateException("Should not enter this slot!"); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { throw new IllegalStateException("Should not exit this slot!"); } } private class MustBlockSlot extends AbstractLinkedProcessorSlot { boolean exited = false; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, boolean prioritized, Object... args) throws Throwable { throw new BlockException("custom") {}; } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { exited = true; } } private class ShouldPassSlot extends AbstractLinkedProcessorSlot { boolean entered = false; boolean exited = false; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, boolean prioritized, Object... args) { entered = true; } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { exited = true; } } @Before public void setUp() throws Exception { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); CtSph.resetChainMap(); } @After public void tearDown() throws Exception { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); CtSph.resetChainMap(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.util.function.BiConsumer; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * Test cases for {@link Entry}. * * @author Eric Zhao */ public class EntryTest { @Test public void testEntryExitCounts() { ResourceWrapper resourceWrapper = new StringResourceWrapper("resA", EntryType.IN); TestEntry entry = new TestEntry(resourceWrapper); entry.exit(); assertEquals(-1, entry.count); entry.exit(9); assertEquals(-10, entry.count); } @Test public void testEntryFieldsGetSet() { ResourceWrapper resourceWrapper = new StringResourceWrapper("resA", EntryType.IN); Entry entry = new TestEntry(resourceWrapper); assertSame(resourceWrapper, entry.getResourceWrapper()); Throwable error = new IllegalStateException(); entry.setError(error); assertSame(error, entry.getError()); Node curNode = mock(Node.class); entry.setCurNode(curNode); assertSame(curNode, entry.getCurNode()); Node originNode = mock(Node.class); entry.setOriginNode(originNode); assertSame(originNode, entry.getOriginNode()); } private class TestEntry extends Entry { int count = 0; TestEntry(ResourceWrapper resourceWrapper) { super(resourceWrapper); } @Override public void exit(int count, Object... args) throws ErrorEntryFreeException { this.count -= count; } @Override protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { return null; } @Override public Node getLastNode() { return null; } @Override public void whenTerminate(BiConsumer consumer) { // do nothing } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.PidUtil; import org.junit.Assert; import org.junit.Test; import java.io.File; import static org.junit.Assert.assertTrue; /** * @author xuyue */ public class RecordLogTest { @Test public void testLogException() { Exception e = new Exception("ex"); RecordLog.info("Error", e); } @Test public void testLogRolling() { int count = 1000; while (--count > 0) { RecordLog.info("Count " + count); } } //Change LogBase It is not not work when integration Testing //Because LogBase.LOG_DIR can be just static init for once and it will not be changed //@Test public void testChangeLogBase() { String userHome = System.getProperty("user.home"); String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis(); System.setProperty(LogBase.LOG_DIR, newLogBase); RecordLog.info("testChangeLogBase"); String logFileName = LogBase.getLogBaseDir(); Assert.assertTrue(newLogBase.equals(logFileName)); File[] files = new File(logFileName).listFiles(); assertTrue(files != null && files.length > 0); deleteLogDir(new File(newLogBase)); } @Test public void testLogBaseDir() { assertTrue(LogBase.getLogBaseDir().startsWith(System.getProperty("user.home"))); } public void testLogNameNotUsePid() { String userHome = System.getProperty("user.home"); String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis(); System.setProperty(LogBase.LOG_DIR, newLogBase); RecordLog.info("testLogNameNotUsePid"); File[] files = new File(newLogBase).listFiles(); assertTrue(files != null && files.length > 0); for (File f : files) { assertTrue(!f.getName().contains("pid" + PidUtil.getPid())); } } public void testLogNameUsePid() { String userHome = System.getProperty("user.home"); String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis(); System.setProperty(LogBase.LOG_DIR, newLogBase); System.setProperty(LogBase.LOG_NAME_USE_PID, "true"); RecordLog.info("testLogNameUsePid"); File[] files = new File(newLogBase).listFiles(); assertTrue(files != null && files.length > 0); for (File f : files) { assertTrue(f.getName().contains("pid" + PidUtil.getPid())); } } private void deleteLogDir(File logDirFile) { if (logDirFile != null && logDirFile.isDirectory()) { if (logDirFile.listFiles() != null) { for (File file : logDirFile.listFiles()) { file.delete(); } } logDirFile.delete(); } } // Because log only writes into the file, // can't read the log(file conflict), so no assertion in this unit test. @Test public void testMessageFormatter() { RecordLog.info("1 2 {} 4 {} 6", "3", "5"); RecordLog.info("1 2 {} 4 {} 6", "3"); RecordLog.info("1 2 {} 4 {} 6"); RecordLog.info("1 2 \\{} 4 {} 6", "5"); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphOTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import static org.junit.Assert.*; import java.lang.reflect.Method; import org.junit.Test; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.context.ContextUtil; /** * Test cases for {@link SphO}. * * @author jialiang.linjl */ public class SphOTest { @Test public void testStringEntryNormal() { if (SphO.entry("resourceName")) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName")); } finally { SphO.exit(); } } } @Test public void testMethodEntryNormal() throws NoSuchMethodException, SecurityException { Method method = SphOTest.class.getMethod("testMethodEntryNormal"); if (SphO.entry(method)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "com.alibaba.csp.sentinel.SphOTest:testMethodEntryNormal()")); } finally { SphO.exit(); } } } @Test public void testStringEntryCount() { if (SphO.entry("resourceName", 2)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.OUT); } finally { SphO.exit(2); } } } @Test public void testMethodEntryCount() throws NoSuchMethodException, SecurityException { Method method = SphOTest.class.getMethod("testMethodEntryCount"); if (SphO.entry(method, 2)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "com.alibaba.csp.sentinel.SphOTest:testMethodEntryCount()")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.OUT); } finally { SphO.exit(2); } } } @Test public void testStringEntryType() { if (SphO.entry("resourceName", EntryType.IN)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(); } } } @Test public void testMethodEntryType() throws NoSuchMethodException, SecurityException { Method method = SphOTest.class.getMethod("testMethodEntryType"); if (SphO.entry(method, EntryType.IN)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "com.alibaba.csp.sentinel.SphOTest:testMethodEntryType()")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(); } } } @Test public void testStringEntryTypeCount() { if (SphO.entry("resourceName", EntryType.IN, 2)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(2); } } } @Test public void testMethodEntryTypeCount() throws NoSuchMethodException, SecurityException { Method method = SphOTest.class.getMethod("testMethodEntryTypeCount"); if (SphO.entry(method, EntryType.IN, 2)) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "com.alibaba.csp.sentinel.SphOTest:testMethodEntryTypeCount()")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(2); } } } @Test public void testStringEntryAll() { if (SphO.entry("resourceName", EntryType.IN, 2, "hello1", "hello2")) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(2, "hello1", "hello2"); } } } @Test public void testMethodEntryAll() throws NoSuchMethodException, SecurityException { Method method = SphOTest.class.getMethod("testMethodEntryAll"); if (SphO.entry(method, EntryType.IN, 2, "hello1", "hello2")) { try { assertTrue(StringUtil.equalsIgnoreCase( ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "com.alibaba.csp.sentinel.SphOTest:testMethodEntryAll()")); assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN); } finally { SphO.exit(2, "hello1", "hello2"); } } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel; import static org.junit.Assert.*; import java.lang.reflect.Method; import org.junit.Test; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Test cases for {@link SphU}. * * @author jialiang.linjl */ public class SphUTest { @Test public void testStringEntryNormal() throws BlockException { Entry e = SphU.entry("resourceName"); assertNotNull(e); assertEquals(e.resourceWrapper.getName(), "resourceName"); assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT); assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME); e.exit(); } @Test public void testMethodEntryNormal() throws BlockException, NoSuchMethodException, SecurityException { Method method = SphUTest.class.getMethod("testMethodEntryNormal"); Entry e = SphU.entry(method); assertNotNull(e); assertTrue(StringUtil .equalsIgnoreCase(e.resourceWrapper.getName(), "com.alibaba.csp.sentinel.SphUTest:testMethodEntryNormal()")); assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT); assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME); e.exit(); } @Test(expected = ErrorEntryFreeException.class) public void testStringEntryNotPairedException() throws BlockException { Entry e = SphU.entry("resourceName"); Entry e1 = SphU.entry("resourceName"); if (e != null) { e.exit(); } if (e1 != null) { e1.exit(); } } @Test public void testStringEntryCount() throws BlockException { Entry e = SphU.entry("resourceName", 2); assertNotNull(e); assertEquals("resourceName", e.resourceWrapper.getName()); assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT); assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME); e.exit(2); } @Test public void testMethodEntryCount() throws BlockException, NoSuchMethodException, SecurityException { Method method = SphUTest.class.getMethod("testMethodEntryNormal"); Entry e = SphU.entry(method, 2); assertNotNull(e); assertTrue(StringUtil .equalsIgnoreCase(e.resourceWrapper.getName(), "com.alibaba.csp.sentinel.SphUTest:testMethodEntryNormal()")); assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT); e.exit(2); } @Test public void testStringEntryType() throws BlockException { Entry e = SphU.entry("resourceName", EntryType.IN); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(); } @Test public void testMethodEntryType() throws BlockException, NoSuchMethodException, SecurityException { Method method = SphUTest.class.getMethod("testMethodEntryNormal"); Entry e = SphU.entry(method, EntryType.IN); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(); } @Test public void testStringEntryCountType() throws BlockException { Entry e = SphU.entry("resourceName", EntryType.IN, 2); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(2); } @Test public void testMethodEntryCountType() throws BlockException, NoSuchMethodException, SecurityException { Method method = SphUTest.class.getMethod("testMethodEntryNormal"); Entry e = SphU.entry(method, EntryType.IN, 2); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(); } @Test public void testStringEntryAll() throws BlockException { final String arg0 = "foo"; final String arg1 = "baz"; Entry e = SphU.entry("resourceName", EntryType.IN, 2, arg0, arg1); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(2, arg0, arg1); } @Test public void testMethodEntryAll() throws BlockException, NoSuchMethodException, SecurityException { final String arg0 = "foo"; final String arg1 = "baz"; Method method = SphUTest.class.getMethod("testMethodEntryNormal"); Entry e = SphU.entry(method, EntryType.IN, 2, arg0, arg1); assertSame(e.resourceWrapper.getEntryType(), EntryType.IN); e.exit(2, arg0, arg1); } @Test public void testEntryExitAutomation() throws BlockException{ String[] args = {"foo", "baz"}; int batchCount = 3; Entry e = SphU.entry("testEntryExitAutomation", EntryType.IN, 3, args); e.exit(); // The number of success is automatically updated based on batchCount when exit assertEquals(batchCount, e.getCurNode().totalSuccess()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/TracerTest.java ================================================ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.util.function.Predicate; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * @author Carpenter Lee */ public class TracerTest extends Tracer { @Before public void setUp() { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); Tracer.exceptionPredicate = null; } @After public void tearDown() { ContextTestUtil.cleanUpContext(); ContextTestUtil.resetContextMap(); Tracer.exceptionPredicate = null; } @Test public void testTraceWhenContextSizeExceedsThreshold() { int i = 0; for (; i < Constants.MAX_CONTEXT_NAME_SIZE; i++) { ContextUtil.enter("test-context-" + i); ContextUtil.exit(); } try { ContextUtil.enter("test-context-" + i); throw new RuntimeException("test"); } catch (Exception e) { Tracer.trace(e); } finally { ContextUtil.exit(); } } @Test public void setExceptionsToTrace() { Tracer.ignoreClasses = null; Tracer.traceClasses = null; Tracer.setExceptionsToTrace(TraceException.class, TraceException2.class); Assert.assertTrue(Tracer.shouldTrace(new TraceException2())); Assert.assertTrue(Tracer.shouldTrace(new TraceExceptionSub())); Assert.assertFalse(Tracer.shouldTrace(new Exception())); } @Test public void setExceptionPredicate() { Predicate throwablePredicate = new Predicate() { @Override public boolean test(Throwable throwable) { if (throwable instanceof TraceException) { return true; } else if (throwable instanceof IgnoreException) { return false; } return false; } }; Tracer.setExceptionPredicate(throwablePredicate); Assert.assertTrue(Tracer.shouldTrace(new TraceException())); Assert.assertFalse(Tracer.shouldTrace(new IgnoreException())); } @Test public void setExceptionsToIgnore() { Tracer.ignoreClasses = null; Tracer.traceClasses = null; Tracer.setExceptionsToIgnore(IgnoreException.class, IgnoreException2.class); Assert.assertFalse(Tracer.shouldTrace(new IgnoreException())); Assert.assertFalse(Tracer.shouldTrace(new IgnoreExceptionSub())); Assert.assertTrue(Tracer.shouldTrace(new Exception())); } @Test public void testBoth() { Tracer.ignoreClasses = null; Tracer.traceClasses = null; Tracer.setExceptionsToTrace(TraceException.class, TraceException2.class, BothException.class); Tracer.setExceptionsToIgnore(IgnoreException.class, IgnoreException2.class, BothException.class); Assert.assertFalse(Tracer.shouldTrace(new IgnoreException())); Assert.assertFalse(Tracer.shouldTrace(new BothException())); Assert.assertTrue(Tracer.shouldTrace(new TraceException())); } @Test(expected = IllegalArgumentException.class) public void testNull() { Tracer.setExceptionsToTrace(null); } @Test(expected = IllegalArgumentException.class) public void testNull1() { Tracer.setExceptionsToTrace(TraceException.class, null); } @Test(expected = IllegalArgumentException.class) public void testNull2() { Tracer.setExceptionsToIgnore(IgnoreException.class, null); } @Test(expected = IllegalArgumentException.class) public void testNull3() { Tracer.setExceptionPredicate(null); } private class TraceException extends Exception {} private class TraceException2 extends Exception {} private class TraceExceptionSub extends TraceException {} private class IgnoreException extends Exception {} private class IgnoreException2 extends Exception {} private class IgnoreExceptionSub extends IgnoreException {} private class BothException extends Exception {} } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java ================================================ package com.alibaba.csp.sentinel.config; import org.junit.Assert; import org.junit.Test; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import static com.alibaba.csp.sentinel.config.SentinelConfig.*; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; import static org.junit.Assert.assertEquals; /** * Test cases for {@link SentinelConfig}. * * @author cdfive */ public class SentinelConfigTest { @Test public void testDefaultConfig() { assertEquals(SentinelConfig.DEFAULT_CHARSET, SentinelConfig.charset()); assertEquals(SentinelConfig.DEFAULT_SINGLE_METRIC_FILE_SIZE, SentinelConfig.singleMetricFileSize()); assertEquals(SentinelConfig.DEFAULT_TOTAL_METRIC_FILE_COUNT, SentinelConfig.totalMetricFileCount()); assertEquals(SentinelConfig.DEFAULT_COLD_FACTOR, SentinelConfig.coldFactor()); assertEquals(SentinelConfig.DEFAULT_STATISTIC_MAX_RT, SentinelConfig.statisticMaxRt()); assertEquals(false, SentinelConfig.shouldSkipRegexIfSimpleRuleMatched()); } // add JVM parameter // -Dcsp.sentinel.charset=gbk // -Dcsp.sentinel.metric.file.single.size=104857600 // -Dcsp.sentinel.metric.file.total.count=10 // -Dcsp.sentinel.flow.cold.factor=5 // -Dcsp.sentinel.statistic.max.rt=10000 // @Test public void testCustomConfig() { assertEquals("gbk", SentinelConfig.charset()); assertEquals(104857600L, SentinelConfig.singleMetricFileSize()); assertEquals(10, SentinelConfig.totalMetricFileCount()); assertEquals(5, SentinelConfig.coldFactor()); assertEquals(10000, SentinelConfig.statisticMaxRt()); } /** * when set code factor alue equal or smaller than 1, get value * in SentinelConfig.coldFactor() will return DEFAULT_COLD_FACTOR * see {@link SentinelConfig#coldFactor()} */ @Test public void testColdFactorEqualOrSmallerThanOne() { SentinelConfig.setConfig(SentinelConfig.COLD_FACTOR, "0.5"); assertEquals(SentinelConfig.DEFAULT_COLD_FACTOR, SentinelConfig.coldFactor()); SentinelConfig.setConfig(SentinelConfig.COLD_FACTOR, "1"); assertEquals(SentinelConfig.DEFAULT_COLD_FACTOR, SentinelConfig.coldFactor()); } @Test public void testColdFactoryLargerThanOne() { SentinelConfig.setConfig(SentinelConfig.COLD_FACTOR, "2"); assertEquals(2, SentinelConfig.coldFactor()); SentinelConfig.setConfig(SentinelConfig.COLD_FACTOR, "4"); assertEquals(4, SentinelConfig.coldFactor()); } //add Jvm parameter //-Dcsp.sentinel.config.file=classpath:sentinel-propertiesTest.properties //-Dcsp.sentinel.flow.cold.factor=5 //-Dcsp.sentinel.statistic.max.rt=1000 //@Test public void testLoadProperties() throws IOException { File file = null; String fileName = "sentinel-propertiesTest.properties"; try { file = new File(addSeparator(System.getProperty("user.dir")) + "target/classes/" + fileName); if (!file.exists()) { file.createNewFile(); } BufferedWriter out = new BufferedWriter(new FileWriter(file)); out.write(buildPropertyStr(CHARSET, "utf-8")); out.write("\n"); out.write(buildPropertyStr(SINGLE_METRIC_FILE_SIZE, "1000")); out.write("\n"); out.write(buildPropertyStr(TOTAL_METRIC_FILE_COUNT, "20")); out.write("\n"); out.write(buildPropertyStr(COLD_FACTOR, "123")); out.write("\n"); out.write(buildPropertyStr(STATISTIC_MAX_RT, "6000")); out.write("\n"); out.write(buildPropertyStr(PROJECT_NAME_PROP_KEY, "sentinel_test")); out.flush(); out.close(); Assert.assertTrue(SentinelConfig.getConfig(CHARSET).equals("utf-8")); Assert.assertTrue(SentinelConfig.getConfig(SINGLE_METRIC_FILE_SIZE).equals("1000")); Assert.assertTrue(SentinelConfig.getConfig(TOTAL_METRIC_FILE_COUNT).equals("20")); Assert.assertTrue(SentinelConfig.getConfig(COLD_FACTOR).equals("5")); Assert.assertTrue(SentinelConfig.getConfig(STATISTIC_MAX_RT).equals("1000")); Assert.assertTrue(SentinelConfig.getAppName().equals("sentinel_test")); } finally { if (file != null) { file.delete(); } } } private String buildPropertyStr(String key, String value) { return key + "=" + value; } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; import com.alibaba.csp.sentinel.Constants; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link Context} and {@link ContextUtil}. * * @author jialiang.linjl * @author Eric Zhao */ public class ContextTest { @Before public void setUp() { resetContextMap(); } @After public void cleanUp() { ContextTestUtil.cleanUpContext(); } @Test public void testEnterCustomContextWhenExceedsThreshold() { fillContext(); try { String contextName = "abc"; ContextUtil.enter(contextName, "bcd"); Context curContext = ContextUtil.getContext(); assertNotEquals(contextName, curContext.getName()); assertTrue(curContext instanceof NullContext); assertEquals("", curContext.getOrigin()); } finally { ContextUtil.exit(); resetContextMap(); } } @Test public void testDefaultContextWhenExceedsThreshold() { fillContext(); try { ContextUtil.trueEnter(Constants.CONTEXT_DEFAULT_NAME, ""); Context curContext = ContextUtil.getContext(); assertEquals(Constants.CONTEXT_DEFAULT_NAME, curContext.getName()); assertNotNull(curContext.getEntranceNode()); } finally { ContextUtil.exit(); resetContextMap(); } } private void fillContext() { for (int i = 0; i < Constants.MAX_CONTEXT_NAME_SIZE; i++) { ContextUtil.enter("test-context-" + i); ContextUtil.exit(); } } private void resetContextMap() { ContextTestUtil.resetContextMap(); } @Test public void testEnterContext() { final String contextName = "contextA"; final String origin = "originA"; ContextUtil.enter(contextName, origin); Context curContext = ContextUtil.getContext(); assertEquals(contextName, curContext.getName()); assertEquals(origin, curContext.getOrigin()); assertFalse(curContext.isAsync()); ContextUtil.exit(); assertNull(ContextUtil.getContext()); } @Test public void testReplaceContext() { final String contextName = "contextA"; final String origin = "originA"; ContextUtil.enter(contextName, origin); Context contextB = Context.newAsyncContext(null, "contextB") .setOrigin("originA"); Context contextA = ContextUtil.replaceContext(contextB); assertEquals(contextName, contextA.getName()); assertEquals(origin, contextA.getOrigin()); assertFalse(contextA.isAsync()); Context curContextAfterReplace = ContextUtil.getContext(); assertEquals(contextB.getName(), curContextAfterReplace.getName()); assertEquals(contextB.getOrigin(), curContextAfterReplace.getOrigin()); assertTrue(curContextAfterReplace.isAsync()); ContextUtil.replaceContext(null); assertNull(ContextUtil.getContext()); } @Test public void testRunOnContext() { final String contextName = "contextA"; final String origin = "originA"; ContextUtil.enter(contextName, origin); final Context contextB = Context.newAsyncContext(null, "contextB") .setOrigin("originB"); assertEquals(contextName, ContextUtil.getContext().getName()); ContextUtil.runOnContext(contextB, new Runnable() { @Override public void run() { Context curContext = ContextUtil.getContext(); assertEquals(contextB.getName(), curContext.getName()); assertEquals(contextB.getOrigin(), curContext.getOrigin()); assertTrue(curContext.isAsync()); } }); assertEquals(contextName, ContextUtil.getContext().getName()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTestUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.context; import com.alibaba.csp.sentinel.Constants; /** * Util class for testing context-related functions. * Only for test. DO NOT USE IN PRODUCTION! * * @author Eric Zhao * @since 0.2.0 */ public final class ContextTestUtil { public static void cleanUpContext() { Context context = ContextUtil.getContext(); if (context != null) { context.setCurEntry(null); ContextUtil.exit(); } } public static void resetContextMap() { ContextUtil.resetContextMap(); Constants.ROOT.removeChildList(); } private ContextTestUtil() {} } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import java.util.TimeZone; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class EagleEyeCoreUtilsTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void testIsBlank() { Assert.assertTrue(EagleEyeCoreUtils.isBlank("")); Assert.assertTrue(EagleEyeCoreUtils.isBlank(" ")); Assert.assertTrue(EagleEyeCoreUtils.isBlank(null)); Assert.assertFalse(EagleEyeCoreUtils.isBlank("foo")); } @Test public void testCheckNotNullEmpty() { Assert.assertEquals("foo", EagleEyeCoreUtils.checkNotNullEmpty("foo", "bar")); } @Test public void testCheckNotNullEmptyInputEmptyStringThrowsException() { thrown.expect(IllegalArgumentException.class); EagleEyeCoreUtils.checkNotNullEmpty("", "bar"); // Method is not expected to return due to exception thrown } @Test public void testCheckNotNullEmptyInputSpaceThrowsException() { thrown.expect(IllegalArgumentException.class); EagleEyeCoreUtils.checkNotNullEmpty(" ", "bar"); // Method is not expected to return due to exception thrown } @Test public void testCheckNotNullEmptyInputNullThrowsException() { thrown.expect(IllegalArgumentException.class); EagleEyeCoreUtils.checkNotNullEmpty(null, "bar"); // Method is not expected to return due to exception thrown } @Test public void testCheckNotNull() { Assert.assertEquals("", EagleEyeCoreUtils.checkNotNull("", "bar")); Assert.assertEquals(" ", EagleEyeCoreUtils.checkNotNull(" ", "bar")); Assert.assertEquals("foo", EagleEyeCoreUtils.checkNotNull("foo", "bar")); } @Test public void testCheckNotNullThrowsException() { thrown.expect(IllegalArgumentException.class); EagleEyeCoreUtils.checkNotNull(null, "bar"); // Method is not expected to return due to exception thrown } @Test public void testDefaultIfNull() { Assert.assertEquals("", EagleEyeCoreUtils.defaultIfNull("", "bar")); Assert.assertEquals(" ", EagleEyeCoreUtils.defaultIfNull(" ", "bar")); Assert.assertEquals("bar", EagleEyeCoreUtils.defaultIfNull(null, "bar")); Assert.assertEquals("foo", EagleEyeCoreUtils.defaultIfNull("foo", "bar")); } @Test public void testIsNotBlank() { Assert.assertFalse(EagleEyeCoreUtils.isNotBlank("")); Assert.assertFalse(EagleEyeCoreUtils.isNotBlank(" ")); Assert.assertFalse(EagleEyeCoreUtils.isNotBlank(null)); Assert.assertTrue(EagleEyeCoreUtils.isNotBlank("foo")); } @Test public void testIsNotEmpty() { Assert.assertFalse(EagleEyeCoreUtils.isNotEmpty("")); Assert.assertFalse(EagleEyeCoreUtils.isNotEmpty(null)); Assert.assertTrue(EagleEyeCoreUtils.isNotEmpty(" ")); Assert.assertTrue(EagleEyeCoreUtils.isNotEmpty("foo")); } @Test public void testTrim() { Assert.assertNull(EagleEyeCoreUtils.trim(null)); Assert.assertEquals("", EagleEyeCoreUtils.trim("")); Assert.assertEquals("", EagleEyeCoreUtils.trim(" ")); Assert.assertEquals("foo", EagleEyeCoreUtils.trim("foo")); Assert.assertEquals("foo", EagleEyeCoreUtils.trim("foo ")); Assert.assertEquals("foo", EagleEyeCoreUtils.trim(" foo")); Assert.assertEquals("foo", EagleEyeCoreUtils.trim(" foo ")); Assert.assertEquals("f o o", EagleEyeCoreUtils.trim(" f o o ")); } @Test public void testSplit() { Assert.assertNull(EagleEyeCoreUtils.split(null, 'a')); Assert.assertArrayEquals(new String[0], EagleEyeCoreUtils.split("", ',')); Assert.assertArrayEquals(new String[]{"foo", "bar", "baz"}, EagleEyeCoreUtils.split("foo,bar,baz", ',')); } @Test public void testAppendWithBlankCheck() { Assert.assertEquals("bar", EagleEyeCoreUtils.appendWithBlankCheck( null, "bar", new StringBuilder()).toString()); Assert.assertEquals("foo", EagleEyeCoreUtils.appendWithBlankCheck( "foo", "bar", new StringBuilder()).toString()); } @Test public void testAppendWithNullCheck() { Assert.assertEquals("bar", EagleEyeCoreUtils.appendWithNullCheck( null, "bar", new StringBuilder()).toString()); Assert.assertEquals("foo", EagleEyeCoreUtils.appendWithNullCheck( "foo", "bar", new StringBuilder()).toString()); } @Test public void testAppendLog() { Assert.assertEquals("foo bar baz foo", EagleEyeCoreUtils.appendLog( "foo\nbar\rbaz,foo", new StringBuilder(), ',').toString()); Assert.assertEquals("", EagleEyeCoreUtils.appendLog( null, new StringBuilder(), ',').toString()); } @Test public void testFormatTime() { Assert.assertEquals("2019-06-15 12:13:14.000", EagleEyeCoreUtils.formatTime(1560600794000L - TimeZone.getDefault().getOffset(1560600794000L))); } @Test public void testGetSystemProperty() { Assert.assertNull(EagleEyeCoreUtils.getSystemProperty(null)); Assert.assertNull(EagleEyeCoreUtils.getSystemProperty("foo")); } @Test public void testGetSystemPropertyForLong() { Assert.assertEquals(2L, EagleEyeCoreUtils.getSystemPropertyForLong(null, 2L)); } @Test public void testIsHexNumeric() { Assert.assertTrue(EagleEyeCoreUtils.isHexNumeric('2')); Assert.assertTrue(EagleEyeCoreUtils.isHexNumeric('b')); Assert.assertFalse(EagleEyeCoreUtils.isHexNumeric('z')); Assert.assertFalse(EagleEyeCoreUtils.isHexNumeric('%')); } @Test public void testIsNumeric() { Assert.assertTrue(EagleEyeCoreUtils.isNumeric('2')); Assert.assertFalse(EagleEyeCoreUtils.isNumeric('b')); Assert.assertFalse(EagleEyeCoreUtils.isNumeric('%')); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/TokenBucketTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.eagleeye; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.TimeUnit; public class TokenBucketTest { @Test(expected = IllegalArgumentException.class) public void testTokenBucketFailToken() { TokenBucket tokenBucket = new TokenBucket(-1, TimeUnit.SECONDS.toMillis(10)); } @Test(expected = IllegalArgumentException.class) public void testTokenBucketFailTime() { TokenBucket tokenBucket = new TokenBucket(10, 10); } @Test public void testAccept() { TokenBucket tokenBucket = new TokenBucket(1, TimeUnit.SECONDS.toMillis(10)); Assert.assertTrue(tokenBucket.accept(System.currentTimeMillis())); Assert.assertFalse(tokenBucket.accept(System.currentTimeMillis())); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/LogBaseTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log; import org.junit.Assert; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import static com.alibaba.csp.sentinel.log.LogBase.*; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; /** * @author lianglin * @since 1.7.0 */ public class LogBaseTest { //add Jvm parameter //-Dcsp.sentinel.config.file=log-propertiesTest.properties //-Dcsp.sentinel.log.charset="utf-8" //-Dcsp.sentinel.log.output.type="file" //@Test public void testLoadProperties() throws IOException { File file = null; String fileName = "log-propertiesTest.properties"; try { file = new File(addSeparator(System.getProperty("user.dir")) + "target/classes/" + fileName); if (!file.exists()) { file.createNewFile(); } BufferedWriter out = new BufferedWriter(new FileWriter(file)); out.write(buildPropertyStr(LOG_DIR, "/data/logs/")); out.write("\n"); out.write(buildPropertyStr(LOG_NAME_USE_PID, "true")); out.write("\n"); out.write(buildPropertyStr(LOG_OUTPUT_TYPE, "console")); out.write("\n"); out.write(buildPropertyStr(LOG_CHARSET, "gbk")); out.flush(); out.close(); //test will make dir //Assert.assertTrue(LogBase.getLogBaseDir().equals("/data/logs/")); Assert.assertTrue(LogBase.isLogNameUsePid()); Assert.assertTrue(LogBase.getLogOutputType().equals("file")); Assert.assertTrue(LogBase.getLogCharset().equals("utf-8")); } finally { if (file != null) { file.delete(); } } } private String buildPropertyStr(String key, String value) { return key + "=" + value; } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.log.jul; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.LogRecord; import com.alibaba.csp.sentinel.log.jul.ConsoleHandler; import com.alibaba.csp.sentinel.log.jul.CspFormatter; import static org.junit.Assert.*; /** * Test cases for {@link ConsoleHandler}. * * @author cdfive */ public class ConsoleHandlerTest { @Test public void testInfoPublish() { ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(baosOut)); ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); System.setErr(new PrintStream(baosErr)); CspFormatter cspFormatter = new CspFormatter(); ConsoleHandler consoleHandler = new ConsoleHandler(); LogRecord logRecord; // Test INFO level, should log to stdout logRecord = new LogRecord(Level.INFO, "test info message"); consoleHandler.publish(logRecord); consoleHandler.close(); assertEquals(cspFormatter.format(logRecord), baosOut.toString()); assertEquals("", baosErr.toString()); baosOut.reset(); baosErr.reset(); } @Test public void testWarnPublish() { ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(baosOut)); ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); System.setErr(new PrintStream(baosErr)); CspFormatter cspFormatter = new CspFormatter(); ConsoleHandler consoleHandler = new ConsoleHandler(); LogRecord logRecord; // Test INFO level, should log to stderr logRecord = new LogRecord(Level.WARNING, "test warning message"); consoleHandler.publish(logRecord); consoleHandler.close(); assertEquals(cspFormatter.format(logRecord), baosErr.toString()); assertEquals("", baosOut.toString()); baosOut.reset(); baosErr.reset(); } @Test public void testFinePublish() { ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(baosOut)); ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); System.setErr(new PrintStream(baosErr)); CspFormatter cspFormatter = new CspFormatter(); ConsoleHandler consoleHandler = new ConsoleHandler(); LogRecord logRecord; // Test FINE level, no log by default // Default log level is INFO, to log FINE message to stdout, add following config in $JAVA_HOME/jre/lib/logging.properties // java.util.logging.StreamHandler.level=FINE logRecord = new LogRecord(Level.FINE, "test fine message"); consoleHandler.publish(logRecord); consoleHandler.close(); assertEquals("", baosOut.toString()); assertEquals("", baosErr.toString()); baosOut.reset(); baosErr.reset(); } @Test public void testSeverePublish() { ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(baosOut)); ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); System.setErr(new PrintStream(baosErr)); CspFormatter cspFormatter = new CspFormatter(); ConsoleHandler consoleHandler = new ConsoleHandler(); LogRecord logRecord; // Test SEVERE level, should log to stderr logRecord = new LogRecord(Level.SEVERE, "test severe message"); consoleHandler.publish(logRecord); consoleHandler.close(); assertEquals(cspFormatter.format(logRecord), baosErr.toString()); assertEquals("", baosOut.toString()); baosOut.reset(); baosErr.reset(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/FakeAdvancedMetricExtension.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author bill_yip * @author Eric Zhao */ class FakeAdvancedMetricExtension implements AdvancedMetricExtension { long pass = 0; long block = 0; long complete = 0; long exception = 0; long rt = 0; long concurrency = 0; @Override public void onPass(ResourceWrapper rw, int batchCount, Object[] args) { this.pass += batchCount; this.concurrency++; } @Override public void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e, Object[] args) { this.block += batchCount; } @Override public void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args) { this.complete += batchCount; this.rt += rt; this.concurrency--; } @Override public void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args) { this.exception += batchCount; } @Override public void addPass(String resource, int n, Object... args) { // Do nothing because of using the enhanced one } @Override public void addBlock(String resource, int n, String origin, BlockException blockException, Object... args) { // Do nothing because of using the enhanced one } @Override public void addSuccess(String resource, int n, Object... args) { // Do nothing because of using the enhanced one } @Override public void addException(String resource, int n, Throwable throwable) { // Do nothing because of using the enhanced one } @Override public void addRt(String resource, long rt, Object... args) { // Do nothing because of using the enhanced one } @Override public void increaseThreadNum(String resource, Object... args) { // Do nothing because of using the enhanced one } @Override public void decreaseThreadNum(String resource, Object... args) { // Do nothing because of using the enhanced one } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/FakeMetricExtension.java ================================================ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.metric.extension.MetricExtension; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Carpenter Lee */ class FakeMetricExtension implements MetricExtension { long pass = 0; long block = 0; long success = 0; long exception = 0; long rt = 0; long thread = 0; @Override public void addPass(String resource, int n, Object... args) { pass += n; } @Override public void addBlock(String resource, int n, String origin, BlockException ex, Object... args) { block += n; } @Override public void addSuccess(String resource, int n, Object... args) { success += n; } @Override public void addException(String resource, int n, Throwable t) { exception += n; } @Override public void addRt(String resource, long rt, Object... args) { this.rt += rt; } @Override public void increaseThreadNum(String resource, Object... args) { thread++; } @Override public void decreaseThreadNum(String resource, Object... args) { thread--; } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallbackTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.junit.Assert; import org.junit.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Carpenter Lee */ public class MetricEntryCallbackTest { @Test public void onPass() throws Exception { FakeMetricExtension extension = new FakeMetricExtension(); FakeAdvancedMetricExtension advancedExtension = new FakeAdvancedMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); MetricExtensionProvider.addMetricExtension(advancedExtension); MetricEntryCallback entryCallback = new MetricEntryCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); int count = 2; Object[] args = {"args1", "args2"}; entryCallback.onPass(null, resourceWrapper, null, count, args); // assert extension Assert.assertEquals(extension.pass, count); Assert.assertEquals(extension.thread, 1); // assert advancedExtension Assert.assertEquals(advancedExtension.pass, count); Assert.assertEquals(advancedExtension.concurrency, 1); } @Test public void onBlocked() throws Exception { FakeMetricExtension extension = new FakeMetricExtension(); FakeAdvancedMetricExtension advancedExtension = new FakeAdvancedMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); MetricExtensionProvider.addMetricExtension(advancedExtension); MetricEntryCallback entryCallback = new MetricEntryCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); Context context = mock(Context.class); when(context.getOrigin()).thenReturn("origin1"); int count = 2; Object[] args = {"args1", "args2"}; entryCallback.onBlocked(new FlowException("xx"), context, resourceWrapper, null, count, args); // assert extension Assert.assertEquals(extension.block, count); // assert advancedExtension Assert.assertEquals(advancedExtension.block, count); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Test; import org.mockito.MockedStatic; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Carpenter Lee */ public class MetricExitCallbackTest extends AbstractTimeBasedTest { @Test public void onExit() { try (MockedStatic mocked = super.mockTimeUtil()) { FakeMetricExtension extension = new FakeMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); MetricExitCallback exitCallback = new MetricExitCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); int count = 2; Object[] args = {"args1", "args2"}; long prevRt = 20; extension.rt = prevRt; extension.success = 6; extension.thread = 10; Context context = mock(Context.class); Entry entry = mock(Entry.class); // Mock current time long curMillis = System.currentTimeMillis(); setCurrentMillis(mocked, curMillis); int deltaMs = 100; when(entry.getError()).thenReturn(null); when(entry.getCreateTimestamp()).thenReturn(curMillis - deltaMs); when(context.getCurEntry()).thenReturn(entry); exitCallback.onExit(context, resourceWrapper, count, args); Assert.assertEquals(prevRt + deltaMs, extension.rt); Assert.assertEquals(extension.success, 6 + count); Assert.assertEquals(extension.thread, 10 - 1); } } /** * @author bill_yip */ @Test public void advancedExtensionOnExit() { try (MockedStatic mocked = super.mockTimeUtil()) { FakeAdvancedMetricExtension extension = new FakeAdvancedMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); MetricExitCallback exitCallback = new MetricExitCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); int count = 2; Object[] args = {"args1", "args2"}; long prevRt = 20; extension.rt = prevRt; extension.complete = 6; extension.concurrency = 10; Context context = mock(Context.class); Entry entry = mock(Entry.class); // Mock current time long curMillis = System.currentTimeMillis(); setCurrentMillis(mocked, curMillis); int deltaMs = 100; when(entry.getError()).thenReturn(null); when(entry.getCreateTimestamp()).thenReturn(curMillis - deltaMs); when(context.getCurEntry()).thenReturn(entry); exitCallback.onExit(context, resourceWrapper, count, args); Assert.assertEquals(prevRt + deltaMs, extension.rt); Assert.assertEquals(extension.complete, 6 + count); Assert.assertEquals(extension.concurrency, 10 - 1); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static org.junit.Assert.*; /** * Test cases for {@link ClusterNode}. * * @author cdfive */ public class ClusterNodeTest { @Test public void testGetOrCreateOriginNodeSingleThread() { ClusterNode clusterNode = new ClusterNode("test"); String origin1 = "origin1"; Node originNode1 = clusterNode.getOrCreateOriginNode(origin1); assertNotNull(originNode1); assertEquals(1, clusterNode.getOriginCountMap().size()); String origin2 = "origin2"; Node originNode2 = clusterNode.getOrCreateOriginNode(origin2); assertNotNull(originNode2); assertEquals(2, clusterNode.getOriginCountMap().size()); assertNotSame(originNode1, originNode2); // test same origin, no StatisticNode added into the originCountMap Node tmpOriginNode = clusterNode.getOrCreateOriginNode(origin1); assertEquals(2, clusterNode.getOriginCountMap().size()); assertSame(tmpOriginNode, originNode1); assertTrue(clusterNode.getOriginCountMap().containsKey(origin1)); assertTrue(clusterNode.getOriginCountMap().containsKey(origin2)); } @Test public void testGetOrCreateOriginNodeMultiThread() { // Note: in JUnit 4, repeat execute a test method is not very convenient // for simple, here use a loop instead // https://stackoverflow.com/questions/1492856/easy-way-of-running-the-same-junit-test-over-and-over // in JUnit 5, use @RepeatedTest(10) // execute 10 times, test will have chance to failed, if remove the lock in ClusterNode final int testTimes = 10; for (int times = 0; times < testTimes; times++) { final ClusterNode clusterNode = new ClusterNode("test"); // Store all distinct nodes by calling ClusterNode#getOrCreateOriginNode. // Here we need a thread-safe concurrent set (created from ConcurrentHashMap). final Set createdNodes = Collections.newSetFromMap(new ConcurrentHashMap()); // 10 threads, 3 origins, 20 tasks (calling 20 times of ClusterNode#getOrCreateOriginNode concurrently) final ExecutorService es = Executors.newFixedThreadPool(10); final List origins = Arrays.asList("origin1", "origin2", "origin3"); int taskCount = 20; final CountDownLatch latch = new CountDownLatch(taskCount); List> tasks = new ArrayList>(taskCount); for (int i = 0; i < taskCount; i++) { final int index = i % origins.size(); tasks.add(new Callable() { @Override public Object call() throws Exception { // one task call one times of ClusterNode#getOrCreateOriginNode Node node = clusterNode.getOrCreateOriginNode(origins.get(index)); // add the result node to the createdNodes set createdNodes.add(node); latch.countDown(); return null; } }); } try { es.invokeAll(tasks); latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } es.shutdown(); // origins.size() origins, the same count as the originCountMap assertEquals(origins.size(), clusterNode.getOriginCountMap().size()); // not use `assertEquals(origins.size(), createdNodes.size());`, but a compare judgement for debug if (origins.size() != createdNodes.size()) { // for debug, we can add a breakpoint here to see the detail info of createdNodes, // if we removed the lock in ClusterNode fail("originCountMap's size should " + origins.size() + ", but actual " + createdNodes.size()); } // verify originCountMap's key for (String origin : origins) { assertTrue(clusterNode.getOriginCountMap().containsKey(origin)); } } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Assert; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test cases for {@link StatisticNode}. * * @author cdfive */ public class StatisticNodeTest { private static final String LOG_PREFIX = "[StatisticNodeTest] "; private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss"); private static final Random RANDOM = new Random(); private static final int THREAD_COUNT = 20; /** * A simple test for statistic threadNum and qps by using StatisticNode * *

          * 20 threads, 30 tasks, every task execute 10 times of bizMethod * one bizMethod execute within 1 second, and within 0.5 second interval to exceute next bizMthod * so that the total time cost will be within 1 minute *

          * *

          * Print the statistic info of StatisticNode and verify some results *

          */ @Test public void testStatisticThreadNumAndQps() { long testStartTime = TimeUtil.currentTimeMillis(); int taskCount = 30; int taskBizExecuteCount = 10; StatisticNode node = new StatisticNode(); ExecutorService bizEs = Executors.newFixedThreadPool(THREAD_COUNT); ExecutorService tickEs = Executors.newSingleThreadExecutor(); tickEs.submit(new TickTask(node)); List bizTasks = new ArrayList<>(taskBizExecuteCount); for (int i = 0; i < taskCount; i++) { bizTasks.add(new BizTask(node, taskBizExecuteCount)); } try { bizEs.invokeAll(bizTasks); } catch (InterruptedException e) { e.printStackTrace(); } log("===================================================="); log("all biz task done, waiting 3 second to exit"); sleep(3000); bizEs.shutdownNow(); tickEs.shutdownNow(); // now no biz method execute, so there is no curThreadNum,passQps,successQps assertEquals(0, node.curThreadNum(), 0.01); assertEquals(0, node.passQps(), 0.01); assertEquals(0, node.successQps(), 0.01); // note: total time cost should be controlled within 1 minute, // as the node.totalRequest() holding statistics of recent 60 seconds int totalRequest = taskCount * taskBizExecuteCount; // verify totalRequest assertEquals(totalRequest, node.totalRequest()); // as all execute success, totalRequest should equal to totalSuccess assertEquals(totalRequest, node.totalSuccess()); // now there are no data in time span, so the minRT should be equals to TIME_DROP_VALVE assertEquals(node.minRt(), SentinelConfig.statisticMaxRt(), 0.01); log("===================================================="); log("testStatisticThreadNumAndQps done, cost " + (TimeUtil.currentTimeMillis() - testStartTime) + "ms"); } private static class BizTask implements Callable { private StatisticNode node; private Integer bizExecuteCount; public BizTask(StatisticNode node, Integer bizExecuteCount) { this.node = node; this.bizExecuteCount = bizExecuteCount; } @Override public Object call() throws Exception { while (true) { node.increaseThreadNum(); node.addPassRequest(1); long startTime = TimeUtil.currentTimeMillis(); bizMethod(); node.decreaseThreadNum(); // decrease one ThreadNum, so curThreadNum should less than THREAD_COUNT assertTrue(node.curThreadNum() < THREAD_COUNT); long rt = TimeUtil.currentTimeMillis() - startTime; node.addRtAndSuccess(rt, 1); // wait random 0.5 second for simulate method call interval, // otherwise the curThreadNum will always be THREAD_COUNT at the beginning sleep(RANDOM.nextInt(500)); bizExecuteCount--; if (bizExecuteCount <= 0) { break; } } return null; } } private static void bizMethod() { // simulate biz method call in random 1 second sleep(RANDOM.nextInt(1000)); } private static class TickTask implements Runnable { private StatisticNode node; public TickTask(StatisticNode node) { this.node = node; } @Override public void run() { while (true) { // print statistic info every 1 second sleep(1000); // the curThreadNum should not greater than THREAD_COUNT assertTrue(node.curThreadNum() <= THREAD_COUNT); logNode(node); } } } private static void sleep(long ms) { try { TimeUnit.MILLISECONDS.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } } private static void logNode(StatisticNode node) { log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps() + ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps() + ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess() + ", avgRt=" + String.format("%.2f", node.avgRt()) + ", minRt=" + node.minRt()); } private static void log(Object obj) { System.out.println(LOG_PREFIX + obj); } /** * com.alibaba.csp.sentinel.node.StatisticNode#curThreadNum using LongAdder replace the AtomicInteger. * now test the LongAdder is fast than AtomicInteger * and get the right statistic or not */ @Test public void testStatisticLongAdder() throws InterruptedException { AtomicInteger atomicInteger = new AtomicInteger(0); StatisticNode statisticNode = new StatisticNode(); ExecutorService bizEs1 = new ThreadPoolExecutor(THREAD_COUNT, THREAD_COUNT, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); ExecutorService bizEs2 = new ThreadPoolExecutor(THREAD_COUNT, THREAD_COUNT, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); int taskCount = 100; for (int i = 0; i < taskCount; i++) { int op = i % 2; bizEs2.submit(new StatisticAtomicIntegerTask(atomicInteger, op, i)); bizEs1.submit(new StatisticLongAdderTask(statisticNode, op, i)); } Thread.sleep(5000); log("LongAdder totalCost : " + StatisticLongAdderTask.totalCost() + "ms"); log("AtomicInteger totalCost : " + StatisticAtomicIntegerTask.totalCost() + "ms"); Assert.assertEquals(statisticNode.curThreadNum(), atomicInteger.get()); } private static class StatisticLongAdderTask implements Runnable { private StatisticNode statisticNode; /** * 0 addition * 1 subtraction */ private int op; private int taskId; private static Map taskCostMap = new ConcurrentHashMap<>(16); public StatisticLongAdderTask(StatisticNode statisticNode, int op, int taskId) { this.statisticNode = statisticNode; this.op = op; this.taskId = taskId; } @Override public void run() { long startTime = System.currentTimeMillis(); int calCount = 100000; for (int i = 0; i < calCount; i++) { if (op == 0) { statisticNode.increaseThreadNum(); } else if (op == 1) { statisticNode.decreaseThreadNum(); } } long cost = System.currentTimeMillis() - startTime; taskCostMap.put(taskId, cost); } public static long totalCost() { long totalCost = 0; for (long cost : taskCostMap.values()) { totalCost += cost; } return totalCost; } } private static class StatisticAtomicIntegerTask implements Runnable { AtomicInteger atomicInteger; /** * 0 addition * 1 subtraction */ private int op; private int taskId; private static Map taskCostMap = new ConcurrentHashMap<>(16); public StatisticAtomicIntegerTask(AtomicInteger atomicInteger, int op, int taskId) { this.atomicInteger = atomicInteger; this.op = op; this.taskId = taskId; } @Override public void run() { long startTime = System.currentTimeMillis(); int calCount = 100000; for (int i = 0; i < calCount; i++) { if (op == 0) { atomicInteger.incrementAndGet(); } else if (op == 1) { atomicInteger.decrementAndGet(); } } long cost = System.currentTimeMillis() - startTime; taskCostMap.put(taskId, cost); } public static long totalCost() { long totalCost = 0; for (long cost : taskCostMap.values()) { totalCost += cost; } return totalCost; } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/metric/MetricNodeTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.node.metric; import com.alibaba.csp.sentinel.ResourceTypeConstants; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class MetricNodeTest { @Test public void testFromFatString() { String line = "1564382218000|2019-07-29 14:36:58|/foo/*|1|0|1|0|0|0|2|1"; MetricNode node = MetricNode.fromFatString(line); assertEquals(ResourceTypeConstants.COMMON_WEB, node.getClassification()); assertEquals(2, node.getConcurrency()); assertEquals(1, node.getSuccessQps()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/metric/MetricWriterTest.java ================================================ package com.alibaba.csp.sentinel.node.metric; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author Carpenter Lee */ public class MetricWriterTest { @Test public void testFileNameCmp() { String[] arr = new String[] { "metrics.log.2018-03-06", "metrics.log.2018-03-07", "metrics.log.2018-03-07.51", "metrics.log.2018-03-07.10", "metrics.log.2018-03-06.100" }; String[] key = new String[] { "metrics.log.2018-03-06", "metrics.log.2018-03-06.100", "metrics.log.2018-03-07", "metrics.log.2018-03-07.10", "metrics.log.2018-03-07.51" }; ArrayList list = new ArrayList(Arrays.asList(arr)); Collections.sort(list, MetricWriter.METRIC_FILE_NAME_CMP); assertEquals(Arrays.asList(key), list); } @Test public void testFileNamePidCmp() { String[] arr = new String[] { "metrics.log.pid1234.2018-03-06", "metrics.log.pid1234.2018-03-07", "metrics.log.pid1234.2018-03-07.51", "metrics.log.pid1234.2018-03-07.10", "metrics.log.pid1234.2018-03-06.100" }; String[] key = new String[] { "metrics.log.pid1234.2018-03-06", "metrics.log.pid1234.2018-03-06.100", "metrics.log.pid1234.2018-03-07", "metrics.log.pid1234.2018-03-07.10", "metrics.log.pid1234.2018-03-07.51" }; ArrayList list = new ArrayList(Arrays.asList(arr)); Collections.sort(list, MetricWriter.METRIC_FILE_NAME_CMP); assertEquals(Arrays.asList(key), list); } @Test public void testFileNameMatches(){ String baseFileName = "Sentinel-SDK-Demo-metrics.log"; String fileName = "Sentinel-SDK-Demo-metrics.log.2018-03-06"; assertTrue(MetricWriter.fileNameMatches(fileName, baseFileName)); String baseFileName2 = "Sentinel-Admin-metrics.log.pid22568"; String fileName2 = "Sentinel-Admin-metrics.log.pid22568.2018-12-24"; assertTrue(MetricWriter.fileNameMatches(fileName2, baseFileName2)); String baseFileName3 = "Sentinel-SDK-Demo-metrics.log"; String fileName3 = "Sentinel-SDK-Demo-metrics.log.2018-03-06.11"; assertTrue(MetricWriter.fileNameMatches(fileName3, baseFileName3)); String baseFileName4 = "Sentinel-SDK-Demo-metrics.log"; String fileName4 = "Sentinel-SDK-Demo-metrics.log.XXX.2018-03-06.11"; assertFalse(MetricWriter.fileNameMatches(fileName4, baseFileName4)); String baseFileName5 = "Sentinel-SDK-Demo-metrics.log"; String fileName5 = "Sentinel-SDK-Demo-metrics.log.2018-03-06.11XXX"; assertFalse(MetricWriter.fileNameMatches(fileName5, baseFileName5)); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; import com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.slots.logger.LogSlot; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; import com.alibaba.csp.sentinel.slots.system.SystemSlot; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link DefaultSlotChainBuilder}. * * @author cdfive */ public class DefaultSlotChainBuilderTest { @Test public void testBuild() { DefaultSlotChainBuilder builder = new DefaultSlotChainBuilder(); ProcessorSlotChain slotChain = builder.build(); assertNotNull(slotChain); // Verify the order of slot AbstractLinkedProcessorSlot next = slotChain.getNext(); assertTrue(next instanceof NodeSelectorSlot); // Store the first NodeSelectorSlot instance NodeSelectorSlot nodeSelectorSlot = (NodeSelectorSlot) next; next = next.getNext(); assertTrue(next instanceof ClusterBuilderSlot); next = next.getNext(); assertTrue(next instanceof LogSlot); next = next.getNext(); assertTrue(next instanceof StatisticSlot); next = next.getNext(); assertTrue(next instanceof AuthoritySlot); next = next.getNext(); assertTrue(next instanceof SystemSlot); next = next.getNext(); assertTrue(next instanceof FlowSlot); next = next.getNext(); assertTrue(next instanceof DefaultCircuitBreakerSlot); next = next.getNext(); assertTrue(next instanceof DegradeSlot); next = next.getNext(); assertNull(next); // Build again to verify different instances ProcessorSlotChain slotChain2 = builder.build(); assertNotNull(slotChain2); // Verify the two ProcessorSlotChain instances are different assertNotSame(slotChain, slotChain2); next = slotChain2.getNext(); assertTrue(next instanceof NodeSelectorSlot); // Store the second NodeSelectorSlot instance NodeSelectorSlot nodeSelectorSlot2 = (NodeSelectorSlot) next; // Verify the two NodeSelectorSlot instances are different assertNotSame(nodeSelectorSlot, nodeSelectorSlot2); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/RuleManagerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.util.*; import java.util.regex.Pattern; import static org.junit.Assert.*; import static org.mockito.Mockito.when; public class RuleManagerTest { private RuleManager ruleManager; @Before public void setUp() throws Exception { ruleManager = new RuleManager<>(); } @Test public void testUpdateRules() throws Exception { // Setup final Map> rulesMap = generateFlowRules(true); // Run the test ruleManager.updateRules(rulesMap); // Verify the results assertEquals(ruleManager.getRules().size(), 2); Field regexRules = RuleManager.class.getDeclaredField("regexRules"); regexRules.setAccessible(true); assertEquals(((Map)regexRules.get(ruleManager)).size(), 1); Field simpleRules = RuleManager.class.getDeclaredField("simpleRules"); simpleRules.setAccessible(true); assertEquals(((Map)simpleRules.get(ruleManager)).size(), 1); } @Test public void testGetRulesWithCache() throws Exception { // Setup final Map> rulesMap = generateFlowRules(true); // Run the test ruleManager.updateRules(rulesMap); // Verify the results Field regexCacheRules = RuleManager.class.getDeclaredField("regexCacheRules"); regexCacheRules.setAccessible(true); assertEquals(((Map)regexCacheRules.get(ruleManager)).size(), 0); ruleManager.getRules("rule"); assertEquals(((Map)regexCacheRules.get(ruleManager)).size(), 1); } @Test public void testRebuildRulesWhenUpdateRules() throws Exception { // Setup final Map> rulesMap = generateFlowRules(true); // Run the test ruleManager.updateRules(rulesMap); ruleManager.getRules("rule2"); ruleManager.updateRules(generateFlowRules(true)); // Verify the results Field regexCacheRules = RuleManager.class.getDeclaredField("regexCacheRules"); regexCacheRules.setAccessible(true); assertEquals(((Map)regexCacheRules.get(ruleManager)).size(), 1); // Clean up regular rules ruleManager.updateRules(generateFlowRules(false)); // Verify the results assertEquals(((Map)regexCacheRules.get(ruleManager)).size(), 0); } @Test public void testValidRegexRule() { // Setup FlowRule flowRule = new FlowRule(); flowRule.setRegex(true); flowRule.setResource("{}"); // Run the test and verify Assert.assertFalse(RuleManager.checkRegexResourceField(flowRule)); flowRule.setResource(".*"); // Run the test and verify Assert.assertTrue(RuleManager.checkRegexResourceField(flowRule)); } @Test public void testHasConfig() { // Setup final Map> rulesMap = generateFlowRules(true); // Run the test and verify the results ruleManager.updateRules(rulesMap); assertTrue(ruleManager.hasConfig("rule1")); assertFalse(ruleManager.hasConfig("rule3")); } private Map> generateFlowRules(boolean withRegex) { Map> result = new HashMap<>(2); FlowRule flowRule1 = new FlowRule("rule1"); flowRule1.setRegex(withRegex); result.put(flowRule1.getResource(), Collections.singletonList(flowRule1)); FlowRule flowRule2 = new FlowRule("rule2"); result.put(flowRule2.getResource(), Collections.singletonList(flowRule2)); return result; } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityPartialIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.authority; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.junit.Test; import java.util.Collections; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author quguai * @date 2023/10/27 11:48 */ public class AuthorityPartialIntegrationTest { @Test public void testRegex() { AuthorityRule authorityRule = new AuthorityRule(); authorityRule.setRegex(true); authorityRule.setStrategy(1); authorityRule.setLimitApp("appA"); authorityRule.setResource(".*"); AuthorityRuleManager.loadRules(Collections.singletonList(authorityRule)); verifyFlow("testRegex_1", "appA", false); verifyFlow("testRegex_2", "appA", false); verifyFlow("testRegex_1", "appB", true); verifyFlow("testRegex_2", "appB", true); } private void verifyFlow(String resource, String origin, boolean shouldPass) { ContextUtil.enter("a", origin); Entry e = null; try { e = SphU.entry(resource); assertTrue(shouldPass); } catch (BlockException e1) { assertFalse(shouldPass); } finally { if (e != null) { e.exit(); } ContextUtil.exit(); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleCheckerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.authority; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link AuthorityRuleChecker}. * * @author Eric Zhao */ public class AuthorityRuleCheckerTest { @Before public void setUp() { ContextTestUtil.cleanUpContext(); } @Test public void testPassCheck() { String origin = "appA"; ContextUtil.enter("entrance", origin); try { String resourceName = "testPassCheck"; AuthorityRule ruleA = new AuthorityRule() .setResource(resourceName) .setLimitApp(origin + ",appB") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRule ruleB = new AuthorityRule() .setResource(resourceName) .setLimitApp("appB") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRule ruleC = new AuthorityRule() .setResource(resourceName) .setLimitApp(origin) .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_BLACK); AuthorityRule ruleD = new AuthorityRule() .setResource(resourceName) .setLimitApp("appC") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_BLACK); assertTrue(AuthorityRuleChecker.passCheck(ruleA, ContextUtil.getContext())); assertFalse(AuthorityRuleChecker.passCheck(ruleB, ContextUtil.getContext())); assertFalse(AuthorityRuleChecker.passCheck(ruleC, ContextUtil.getContext())); assertTrue(AuthorityRuleChecker.passCheck(ruleD, ContextUtil.getContext())); } finally { ContextUtil.exit(); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManagerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.authority; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link AuthorityRuleManager}. * * @author Eric Zhao */ public class AuthorityRuleManagerTest { @After public void setUp() { AuthorityRuleManager.loadRules(null); } @Test public void testLoadRules() { String resourceName = "testLoadRules"; AuthorityRule rule = new AuthorityRule(); rule.setResource(resourceName); rule.setLimitApp("a,b"); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); List rules = AuthorityRuleManager.getRules(); assertEquals(1, rules.size()); assertEquals(rule, rules.get(0)); AuthorityRuleManager.loadRules(Collections.singletonList(new AuthorityRule())); rules = AuthorityRuleManager.getRules(); assertEquals(0, rules.size()); } @Test public void testIsValidRule() { AuthorityRule ruleA = new AuthorityRule(); AuthorityRule ruleB = null; AuthorityRule ruleC = new AuthorityRule(); ruleC.setResource("abc"); AuthorityRule ruleD = new AuthorityRule(); ruleD.setResource("bcd").setLimitApp("abc"); assertFalse(AuthorityRuleManager.isValidRule(ruleA)); assertFalse(AuthorityRuleManager.isValidRule(ruleB)); assertFalse(AuthorityRuleManager.isValidRule(ruleC)); assertTrue(AuthorityRuleManager.isValidRule(ruleD)); } @After public void tearDown() { AuthorityRuleManager.loadRules(null); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlotTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.authority; import java.util.Collections; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Test cases for {@link AuthoritySlot}. * * @author Eric Zhao */ public class AuthoritySlotTest { private AuthoritySlot authoritySlot = new AuthoritySlot(); @Test public void testCheckAuthorityNoExceptionItemsSuccess() throws Exception { String origin = "appA"; String resourceName = "testCheckAuthorityNoExceptionItemsSuccess"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); ContextUtil.enter("entrance", origin); try { AuthorityRule ruleA = new AuthorityRule() .setResource(resourceName) .setLimitApp(origin + ",appB") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(ruleA)); authoritySlot.checkBlackWhiteAuthority(resourceWrapper, ContextUtil.getContext()); AuthorityRule ruleB = new AuthorityRule() .setResource(resourceName) .setLimitApp("appD") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_BLACK); AuthorityRuleManager.loadRules(Collections.singletonList(ruleB)); authoritySlot.checkBlackWhiteAuthority(resourceWrapper, ContextUtil.getContext()); } finally { ContextUtil.exit(); } } @Test(expected = AuthorityException.class) public void testCheckAuthorityNoExceptionItemsBlackFail() throws Exception { String origin = "appA"; String resourceName = "testCheckAuthorityNoExceptionItemsBlackFail"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); ContextUtil.enter("entrance", origin); try { AuthorityRule ruleA = new AuthorityRule() .setResource(resourceName) .setLimitApp(origin + ",appC") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_BLACK); AuthorityRuleManager.loadRules(Collections.singletonList(ruleA)); authoritySlot.checkBlackWhiteAuthority(resourceWrapper, ContextUtil.getContext()); } finally { ContextUtil.exit(); } } @Test(expected = AuthorityException.class) public void testCheckAuthorityNoExceptionItemsWhiteFail() throws Exception { String origin = "appA"; String resourceName = "testCheckAuthorityNoExceptionItemsWhiteFail"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); ContextUtil.enter("entrance", origin); try { AuthorityRule ruleB = new AuthorityRule() .setResource(resourceName) .setLimitApp("appB, appE") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(ruleB)); authoritySlot.checkBlackWhiteAuthority(resourceWrapper, ContextUtil.getContext()); } finally { ContextUtil.exit(); } } /*@Test public void testCheckAuthorityWithExceptionItemsSuccess() throws Exception { String origin = "ipA"; String resourceName = "interfaceA"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); ContextUtil.enter("entrance", origin); try { AuthorityRule ruleEx = new AuthorityRule() .setResource("methodXXX") .setLimitApp(origin) .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRule ruleA = new AuthorityRule() .setResource(resourceName) .setLimitApp("appA,appB") .as(AuthorityRule.class) .setStrategy(RuleConstant.AUTHORITY_BLACK) .addExceptionItem(ruleEx); AuthorityRuleManager.loadRules(Collections.singletonList(ruleA)); authoritySlot.checkBlackWhiteAuthority(resourceWrapper, ContextUtil.getContext()); } finally { ContextUtil.exit(); } }*/ @Before public void setUp() { ContextTestUtil.cleanUpContext(); AuthorityRuleManager.loadRules(null); } @After public void tearDown() { ContextTestUtil.cleanUpContext(); AuthorityRuleManager.loadRules(null); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/CircuitBreakingIntegrationTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.*; /** * @author jialiang.linjl * @author Eric Zhao */ public class CircuitBreakingIntegrationTest extends AbstractTimeBasedTest { @Before public void setUp() { DegradeRuleManager.loadRules(new ArrayList<>()); } @After public void tearDown() { DegradeRuleManager.loadRules(new ArrayList<>()); DefaultCircuitBreakerRuleManager.loadRules(new ArrayList<>()); } @Test public void testSlowRequestMode() { try (MockedStatic mocked = super.mockTimeUtil()) { CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); setCurrentMillis(mocked, System.currentTimeMillis() / 1000 * 1000); int retryTimeoutSec = 5; int maxRt = 50; int statIntervalMs = 20000; int minRequestAmount = 10; String res = "CircuitBreakingIntegrationTest_testSlowRequestMode"; EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); DegradeRuleManager.loadRules(Arrays.asList( new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRt) .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) .setSlowRatioThreshold(0.8d).setGrade(0) )); // Try first N requests where N = minRequestAmount. for (int i = 0; i < minRequestAmount; i++) { if (i < 7) { assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); } else { assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(-20, -10))); } } // Till now slow ratio should be 70%. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); // Circuit breaker has transformed to OPEN since here. verify(observer) .onStateChange(eq(State.CLOSED), eq(State.OPEN), any(DegradeRule.class), anyDouble()); assertEquals(State.OPEN, DegradeRuleManager.getCircuitBreakers(res).get(0).currentState()); assertFalse(entryAndSleepFor(mocked, res, 1)); sleepSecond(mocked, 1); assertFalse(entryAndSleepFor(mocked, res, 1)); sleepSecond(mocked, retryTimeoutSec); // Test HALF-OPEN to OPEN. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.OPEN), any(DegradeRule.class), anyDouble()); // Wait for next retry timeout; reset(observer); sleepSecond(mocked, retryTimeoutSec + 1); assertTrue(entryAndSleepFor(mocked, res, maxRt - ThreadLocalRandom.current().nextInt(10, 20))); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.CLOSED), any(DegradeRule.class), nullable(Double.class)); // Now circuit breaker has been closed. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); EventObserverRegistry.getInstance().removeStateChangeObserver(res); } } @Test public void testSlowRequestModeUseDefaultRule() { try (MockedStatic mocked = super.mockTimeUtil()) { CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); setCurrentMillis(mocked, System.currentTimeMillis() / 1000 * 1000); int retryTimeoutSec = 5; int maxRt = 50; int statIntervalMs = 20000; int minRequestAmount = 10; String res = "CircuitBreakingIntegrationTest_testSlowRequestModeUseDefaultRule"; EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); DefaultCircuitBreakerRuleManager.loadRules(Arrays.asList( new DegradeRule(DefaultCircuitBreakerRuleManager.DEFAULT_KEY).setTimeWindow(retryTimeoutSec).setCount(maxRt) .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) .setSlowRatioThreshold(0.8d).setGrade(0))); // Try first N requests where N = minRequestAmount. for (int i = 0; i < minRequestAmount; i++) { if (i < 7) { assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); } else { assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(-20, -10))); } } // Till now slow ratio should be 70%. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); // Circuit breaker has transformed to OPEN since here. verify(observer) .onStateChange(eq(State.CLOSED), eq(State.OPEN), any(DegradeRule.class), anyDouble()); assertEquals(State.OPEN, DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(res).get(0).currentState()); assertFalse(entryAndSleepFor(mocked, res, 1)); sleepSecond(mocked, 1); assertFalse(entryAndSleepFor(mocked, res, 1)); sleepSecond(mocked, retryTimeoutSec); // Test HALF-OPEN to OPEN. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.OPEN), any(DegradeRule.class), anyDouble()); // Wait for next retry timeout; reset(observer); sleepSecond(mocked, retryTimeoutSec + 1); assertTrue(entryAndSleepFor(mocked, res, maxRt - ThreadLocalRandom.current().nextInt(10, 20))); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.CLOSED), any(DegradeRule.class), nullable(Double.class)); // Now circuit breaker has been closed. assertTrue(entryAndSleepFor(mocked, res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); EventObserverRegistry.getInstance().removeStateChangeObserver(res); } } @Test public void testExceptionRatioMode() { try (MockedStatic mocked = super.mockTimeUtil()) { CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); setCurrentMillis(mocked, System.currentTimeMillis() / 1000 * 1000); int retryTimeoutSec = 5; double maxRatio = 0.5; int statIntervalMs = 25000; final int minRequestAmount = 10; String res = "CircuitBreakingIntegrationTest_testExceptionRatioMode"; EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); DegradeRuleManager.loadRules(Arrays.asList( new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRatio) .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) )); // Try first N requests where N = minRequestAmount. for (int i = 0; i < minRequestAmount - 1; i++) { if (i < 6) { assertTrue(entryWithErrorIfPresent(mocked, res, new IllegalArgumentException())); } else { assertTrue(entryWithErrorIfPresent(mocked, res, null)); } } // Till now slow ratio should be 60%. assertTrue(entryWithErrorIfPresent(mocked, res, new IllegalArgumentException())); // Circuit breaker has transformed to OPEN since here. assertEquals(State.OPEN, DegradeRuleManager.getCircuitBreakers(res).get(0).currentState()); assertFalse(entryWithErrorIfPresent(mocked, res, null)); sleepSecond(mocked, 2); assertFalse(entryWithErrorIfPresent(mocked, res, null)); sleepSecond(mocked, retryTimeoutSec); // Test HALF-OPEN to OPEN. assertTrue(entryWithErrorIfPresent(mocked, res, new IllegalArgumentException())); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.OPEN), any(DegradeRule.class), anyDouble()); // Wait for next retry timeout; reset(observer); sleepSecond(mocked, retryTimeoutSec + 1); assertTrue(entryWithErrorIfPresent(mocked, res, null)); verify(observer) .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); verify(observer) .onStateChange(eq(State.HALF_OPEN), eq(State.CLOSED), any(DegradeRule.class), nullable(Double.class)); // Now circuit breaker has been closed. assertTrue(entryWithErrorIfPresent(mocked, res, new IllegalArgumentException())); EventObserverRegistry.getInstance().removeStateChangeObserver(res); } } @Test public void testExceptionCountMode() { // TODO } private void verifyState(List breakers, int target) { int state = 0; for (CircuitBreaker breaker : breakers) { if (breaker.currentState() == State.OPEN) { state ++; } else if (breaker.currentState() == State.HALF_OPEN) { state --; } else { state -= 2; } } assertEquals(target, state); } @Test public void testMultipleHalfOpenedBreakers() { try (MockedStatic mocked = super.mockTimeUtil()) { CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); setCurrentMillis(mocked, System.currentTimeMillis() / 1000 * 1000); int retryTimeoutSec = 2; int maxRt = 50; int statIntervalMs = 20000; int minRequestAmount = 1; String res = "CircuitBreakingIntegrationTest_testMultipleHalfOpenedBreakers"; EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); // initial two rules DegradeRuleManager.loadRules(Arrays.asList( new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRt) .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) .setSlowRatioThreshold(0.8d).setGrade(0), new DegradeRule(res).setTimeWindow(retryTimeoutSec * 2).setCount(maxRt) .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) .setSlowRatioThreshold(0.8d).setGrade(0) )); assertTrue(entryAndSleepFor(mocked, res, 100)); // they are open now for (CircuitBreaker breaker : DegradeRuleManager.getCircuitBreakers(res)) { assertEquals(CircuitBreaker.State.OPEN, breaker.currentState()); } sleepSecond(mocked, 3); for (int i = 0; i < 10; i++) { assertFalse(entryAndSleepFor(mocked, res, 100)); } // Now one is in open state while the other experiences open -> half-open -> open verifyState(DegradeRuleManager.getCircuitBreakers(res), 2); sleepSecond(mocked, 3); // They will all recover for (int i = 0; i < 10; i++) { assertTrue(entryAndSleepFor(mocked, res, 1)); } verifyState(DegradeRuleManager.getCircuitBreakers(res), -4); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DefaultCircuitBreakerRuleManagerTest.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; /** * @author Eric Zhao */ public class DefaultCircuitBreakerRuleManagerTest { private final String RESOURCE_NAME = "method_"; @Before public void setUp() throws Exception { List rules = new ArrayList(); DegradeRule rule = new DegradeRule(DefaultCircuitBreakerRuleManager.DEFAULT_KEY) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) .setCount(50) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); assertTrue(DegradeRuleManager.isValidRule(rule)); rules.add(rule); DefaultCircuitBreakerRuleManager.loadRules(rules); } @After public void tearDown() throws Exception { DefaultCircuitBreakerRuleManager.loadRules(new ArrayList()); DegradeRuleManager.loadRules(new ArrayList()); } @Test public void testIsValidRule() { DegradeRule rule1 = new DegradeRule("xx") .setCount(0.1d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(2); DegradeRule rule2 = new DegradeRule(DefaultCircuitBreakerRuleManager.DEFAULT_KEY) .setCount(0.1d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(2); assertFalse(DefaultCircuitBreakerRuleManager.isValidDefaultRule(rule1)); assertTrue(DefaultCircuitBreakerRuleManager.isValidDefaultRule(rule2)); } @Test public void testGetDefaultCircuitBreakers() { String resourceName = RESOURCE_NAME + "I"; assertFalse(DegradeRuleManager.hasConfig(resourceName)); List defaultCircuitBreakers1 = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers( resourceName); assertNotNull(defaultCircuitBreakers1); List defaultCircuitBreakers2 = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers( resourceName); assertSame(defaultCircuitBreakers1, defaultCircuitBreakers2); } @Test public void testGetDefaultCircuitBreakersWhileAddingCustomizedRule() { String resourceNameI = RESOURCE_NAME + "I"; List rules = new ArrayList(); DegradeRule rule = new DegradeRule(resourceNameI) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) .setCount(50) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); rules.add(rule); DegradeRuleManager.loadRules(rules); assertTrue(DegradeRuleManager.hasConfig(resourceNameI)); String resourceNameII = RESOURCE_NAME + "II"; List rules2 = new ArrayList(); DegradeRule rule2 = new DegradeRule(resourceNameII) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) .setCount(50) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); rules2.add(rule2); DegradeRuleManager.loadRules(rules2); assertFalse(DegradeRuleManager.hasConfig(resourceNameI)); assertNotNull(DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resourceNameI)); DegradeRuleManager.loadRules(rules); DefaultCircuitBreakerRuleManager.addExcludedResource(resourceNameII); assertFalse(DegradeRuleManager.hasConfig(resourceNameII)); assertNull(DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resourceNameII)); } @Test public void testGetDefaultCircuitBreakersWhileRemovingCustomizedRule() { String resourceNameI = RESOURCE_NAME + "I"; DefaultCircuitBreakerRuleManager.addExcludedResource(resourceNameI); List rules = new ArrayList(); DegradeRule rule = new DegradeRule(resourceNameI) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) .setCount(50) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); rules.add(rule); DegradeRuleManager.loadRules(rules); assertTrue(DegradeRuleManager.hasConfig(resourceNameI)); assertNull(DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resourceNameI)); //remove customized rule and do not recover default rule List rules2 = new ArrayList(); DegradeRuleManager.loadRules(rules2); assertFalse(DegradeRuleManager.hasConfig(resourceNameI)); assertNull(DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resourceNameI)); //recover default rule DefaultCircuitBreakerRuleManager.removeExcludedResource(resourceNameI); assertFalse(DegradeRuleManager.hasConfig(resourceNameI)); assertNotNull(DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resourceNameI)); } @Test public void testLoadRules() { DegradeRule rule = mock(DegradeRule.class); List ruleList = new ArrayList(); ruleList.add(rule); assertTrue(DefaultCircuitBreakerRuleManager.loadRules(ruleList)); assertFalse(DefaultCircuitBreakerRuleManager.loadRules(ruleList)); } @Test public void testLoadRulesUseDifferentCircuitBreakers() throws Exception { String resA = "resA"; String resB = "resB"; List cbsForResourceA = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resA); assertNotNull(cbsForResourceA); List cbsForResourceB = DefaultCircuitBreakerRuleManager.getDefaultCircuitBreakers(resB); assertNotNull(cbsForResourceB); assertNotEquals(cbsForResourceA, cbsForResourceB); Field cbsField; try { cbsField = DefaultCircuitBreakerRuleManager.class.getDeclaredField("circuitBreakers"); } catch (NoSuchFieldException e) { e.printStackTrace(); throw new Exception(); } cbsField.setAccessible(true); Map> cbs = (Map>) cbsField.get( DefaultCircuitBreakerRuleManager.class); assertEquals(2, cbs.size()); List rules = new ArrayList(); DegradeRule rule = new DegradeRule(DefaultCircuitBreakerRuleManager.DEFAULT_KEY) //rule is different in strategy .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType()) .setCount(0.1d) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); assertTrue(DegradeRuleManager.isValidRule(rule)); rules.add(rule); DefaultCircuitBreakerRuleManager.loadRules(rules); try { cbsField = DefaultCircuitBreakerRuleManager.class.getDeclaredField("circuitBreakers"); } catch (NoSuchFieldException e) { e.printStackTrace(); throw new Exception(); } cbsField.setAccessible(true); Map> newCbs = (Map>) cbsField.get( DefaultCircuitBreakerRuleManager.class); assertEquals(2, newCbs.size()); assertNotEquals(cbs, newCbs); List resACbs = newCbs.get(resA); assertNotNull(resACbs); List resBCbs = newCbs.get(resB); assertNotNull(resBCbs); assertNotEquals(resACbs, resBCbs); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DefaultCircuitBreakerSlotTest.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.mock; /** * @author Eric Zhao */ public class DefaultCircuitBreakerSlotTest { @Before public void setUp() throws Exception { List rules = new ArrayList(); DegradeRule rule = new DegradeRule(DefaultCircuitBreakerRuleManager.DEFAULT_KEY) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) .setCount(50) .setTimeWindow(10) .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); rules.add(rule); DefaultCircuitBreakerRuleManager.loadRules(rules); } @After public void tearDown() throws Exception { DefaultCircuitBreakerRuleManager.loadRules(new ArrayList()); } @Test public void testPerformChecking() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { DefaultCircuitBreakerSlot defaultCircuitBreakerSlot = mock(DefaultCircuitBreakerSlot.class); Context context = mock(Context.class); String resA = "resA"; Method pCMethod = DefaultCircuitBreakerSlot.class.getDeclaredMethod("performChecking", Context.class, ResourceWrapper.class); pCMethod.setAccessible(true); pCMethod.invoke(defaultCircuitBreakerSlot, context, new StringResourceWrapper(resA, EntryType.IN)); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradePartialIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author quguai * @date 2023/10/27 13:56 */ public class DegradePartialIntegrationTest { @Before public void setUp() throws Exception { DegradeRuleManager.loadRules(new ArrayList<>()); } @After public void tearDown() throws Exception { DegradeRuleManager.loadRules(new ArrayList<>()); } @Test public void testDegradeRegex() { DegradeRule rule = new DegradeRule(".*") .setCount(0.5d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setStatIntervalMs(20 * 1000) .setTimeWindow(10) .setMinRequestAmount(1); rule.setRegex(true); DegradeRuleManager.loadRules(Collections.singletonList(rule)); verifyDegradeFlow("testDegradeRegex_1", true, true); verifyDegradeFlow("testDegradeRegex_1", true, false); verifyDegradeFlow("testDegradeRegex_2", true, true); verifyDegradeFlow("testDegradeRegex_2", true, false); } private void verifyDegradeFlow(String resource, boolean error, boolean shouldPass) { Entry entry = null; try { entry = SphU.entry(resource); assertTrue(shouldPass); if (error) { int i = 10 / 0; } } catch (BlockException e1) { assertFalse(shouldPass); } catch (Exception ex) { Tracer.traceEntry(ex, entry); } finally { if (entry != null) { entry.exit(); } } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import java.util.ArrayList; import java.util.Arrays; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link DegradeRuleManager}. * * @author Eric Zhao */ public class DegradeRuleManagerTest { @Before public void setUp() throws Exception { DegradeRuleManager.loadRules(new ArrayList()); } @After public void tearDown() throws Exception { DegradeRuleManager.loadRules(new ArrayList()); } @Test public void loadSameRuleUseSameCircuitBreaker() { String resource = "loadSameRuleUseSameCircuitBreaker"; DegradeRule rule = new DegradeRule(resource) .setCount(100) .setSlowRatioThreshold(0.9d) .setTimeWindow(20) .setStatIntervalMs(20000); DegradeRuleManager.loadRules(Arrays.asList(rule)); CircuitBreaker cb = DegradeRuleManager.getCircuitBreakers(resource).get(0); DegradeRuleManager.loadRules(Arrays.asList(rule, new DegradeRule("abc").setTimeWindow(20).setCount(20).setSlowRatioThreshold(0.8d))); assertSame(cb, DegradeRuleManager.getCircuitBreakers(resource).get(0)); } @Test public void testIsValidRule() { DegradeRule rule1 = new DegradeRule("abc"); DegradeRule rule2 = new DegradeRule("cde") .setCount(100) .setGrade(RuleConstant.DEGRADE_GRADE_RT) .setTimeWindow(-1); DegradeRule rule3 = new DegradeRule("xx") .setCount(1.1) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(2); DegradeRule rule4 = new DegradeRule("yy") .setCount(-3) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) .setTimeWindow(2); DegradeRule rule5 = new DegradeRule("Sentinel") .setCount(97) .setGrade(RuleConstant.DEGRADE_GRADE_RT) .setSlowRatioThreshold(15) .setTimeWindow(15); DegradeRule rule6 = new DegradeRule("Sentinel") .setCount(0.93d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(20) .setMinRequestAmount(0); DegradeRule rule7 = new DegradeRule("Sentinel") .setCount(100) .setSlowRatioThreshold(0.8d) .setTimeWindow(10) .setStatIntervalMs(0) .setMinRequestAmount(20); assertFalse(DegradeRuleManager.isValidRule(rule1)); assertFalse(DegradeRuleManager.isValidRule(rule2)); assertFalse(DegradeRuleManager.isValidRule(rule3)); assertTrue(DegradeRuleManager.isValidRule(rule3.setCount(1.0d))); assertTrue(DegradeRuleManager.isValidRule(rule3.setCount(0.0d))); assertFalse(DegradeRuleManager.isValidRule(rule4)); assertFalse(DegradeRuleManager.isValidRule(rule5)); assertFalse(DegradeRuleManager.isValidRule(rule6)); assertFalse(DegradeRuleManager.isValidRule(rule7)); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class DegradeRuleTest { @Test public void testRuleEquals() { DegradeRule degradeRule1 = new DegradeRule(); DegradeRule degradeRule2 = new DegradeRule(); int minRequestAmount = 20; double count = 1.0; int timeWindow = 2; degradeRule1.setMinRequestAmount(minRequestAmount); degradeRule1.setCount(count); degradeRule1.setTimeWindow(timeWindow); degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_RT); degradeRule2.setMinRequestAmount(minRequestAmount); degradeRule2.setCount(count); degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT); degradeRule2.setTimeWindow(timeWindow); assertEquals(degradeRule1, degradeRule2); degradeRule2.setMinRequestAmount(100); assertNotEquals(degradeRule1, degradeRule2); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreakerTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import org.mockito.MockedStatic; /** * @author Eric Zhao */ public class ExceptionCircuitBreakerTest extends AbstractTimeBasedTest { @Before public void setUp() { DegradeRuleManager.loadRules(new ArrayList()); } @After public void tearDown() throws Exception { DegradeRuleManager.loadRules(new ArrayList()); } @Test public void testRecordErrorOrSuccess() throws BlockException { try (MockedStatic mocked = super.mockTimeUtil()) { String resource = "testRecordErrorOrSuccess"; int retryTimeoutMillis = 10 * 1000; int retryTimeout = retryTimeoutMillis / 1000; DegradeRule rule = new DegradeRule("abc") .setCount(0.2d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setStatIntervalMs(20 * 1000) .setTimeWindow(retryTimeout) .setMinRequestAmount(1); rule.setResource(resource); DegradeRuleManager.loadRules(Arrays.asList(rule)); assertTrue(entryAndSleepFor(mocked, resource, 10)); assertTrue(entryWithErrorIfPresent(mocked, resource, new IllegalArgumentException())); // -> open assertFalse(entryWithErrorIfPresent(mocked, resource, new IllegalArgumentException())); assertFalse(entryAndSleepFor(mocked, resource, 100)); sleep(mocked, retryTimeoutMillis / 2); assertFalse(entryAndSleepFor(mocked, resource, 100)); sleep(mocked, retryTimeoutMillis / 2); assertTrue(entryWithErrorIfPresent(mocked, resource, new IllegalArgumentException())); // -> half -> open assertFalse(entryAndSleepFor(mocked, resource, 100)); assertFalse(entryAndSleepFor(mocked, resource, 100)); sleep(mocked, retryTimeoutMillis); assertTrue(entryAndSleepFor(mocked, resource, 100)); // -> half -> closed assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryAndSleepFor(mocked, resource, 100)); assertTrue(entryWithErrorIfPresent(mocked, resource, new IllegalArgumentException())); assertTrue(entryAndSleepFor(mocked, resource, 100)); } } @Test public void testMaxErrorRatioThreshold() { try (MockedStatic mocked = super.mockTimeUtil()) { String resource = "testMaxErrorRatioThreshold"; DegradeRule rule = new DegradeRule("resource") .setCount(1) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setMinRequestAmount(3) .setStatIntervalMs(5000) .setTimeWindow(5); rule.setResource(resource); DegradeRuleManager.loadRules(Collections.singletonList(rule)); assertTrue(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); assertTrue(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); assertTrue(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); // should be blocked, cause 3/3 requests' rt is bigger than max rt. assertFalse(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); assertFalse(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); sleep(mocked, 5000); assertTrue(entryWithErrorIfPresent(mocked, resource, new RuntimeException())); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreakerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.Collections; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author xierz * @date 2020/10/4 */ public class ResponseTimeCircuitBreakerTest extends AbstractTimeBasedTest { @Before public void setUp() { DegradeRuleManager.loadRules(new ArrayList()); } @After public void tearDown() throws Exception { DegradeRuleManager.loadRules(new ArrayList()); } @Test public void testMaxSlowRatioThreshold() { try (MockedStatic mocked = super.mockTimeUtil()) { String resource = "testMaxSlowRatioThreshold"; DegradeRule rule = new DegradeRule("resource") .setCount(10) .setGrade(RuleConstant.DEGRADE_GRADE_RT) .setMinRequestAmount(3) .setSlowRatioThreshold(1) .setStatIntervalMs(5000) .setTimeWindow(5); rule.setResource(resource); DegradeRuleManager.loadRules(Collections.singletonList(rule)); assertTrue(entryAndSleepFor(mocked, resource, 20)); assertTrue(entryAndSleepFor(mocked, resource, 20)); assertTrue(entryAndSleepFor(mocked, resource, 20)); // should be blocked, cause 3/3 requests' rt is bigger than max rt. assertFalse(entryAndSleepFor(mocked, resource, 20)); sleep(mocked, 1000); assertFalse(entryAndSleepFor(mocked, resource, 20)); sleep(mocked, 4000); assertTrue(entryAndSleepFor(mocked, resource, 20)); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowPartialIntegrationTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.CountDownLatch; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import static org.junit.Assert.*; /** * @author jialiang.linjl */ public class FlowPartialIntegrationTest { @Before public void setUp() throws Exception { FlowRuleManager.loadRules(new ArrayList()); } @After public void tearDown() throws Exception { FlowRuleManager.loadRules(new ArrayList()); } @Test public void testQPSGrade() { FlowRule flowRule = new FlowRule(); flowRule.setResource("testQPSGrade"); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(1); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); Entry e = null; try { e = SphU.entry("testQPSGrade"); } catch (BlockException e1) { assertTrue(false); } e.exit(); try { e = SphU.entry("testQPSGrade"); assertTrue(false); } catch (BlockException e1) { assertTrue(true); } } @Test(expected = BlockException.class) public void testThreadGrade() throws InterruptedException, BlockException { FlowRule flowRule = new FlowRule(); flowRule.setResource("testThreadGrade"); flowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); flowRule.setCount(1); FlowRuleManager.loadRules(Arrays.asList(flowRule)); final CountDownLatch latch = new CountDownLatch(1); Runnable runnable = new Runnable() { @Override public void run() { Entry e = null; try { e = SphU.entry("testThreadGrade"); latch.countDown(); Thread.sleep(100); } catch (BlockException e1) { fail("Should had failed"); } catch (InterruptedException e1) { fail("Should had failed"); } e.exit(); } }; Thread thread = new Thread(runnable); thread.start(); latch.await(); SphU.entry("testThreadGrade"); System.out.println("done"); } @Test public void testQpsRegex() { FlowRule flowRule = new FlowRule(); String resource = ".*"; flowRule.setResource(resource); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setRegex(true); flowRule.setCount(1); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); verifyFlow("testQpsRegex_1", true); verifyFlow("testQpsRegex_2", true); verifyFlow("testQpsRegex_1", false); verifyFlow("testQpsRegex_2", false); } @Test public void testOriginFlowRule() { String RESOURCE_NAME = "testOriginFlowRule"; // normal FlowRule flowRule = new FlowRule(); flowRule.setResource(RESOURCE_NAME); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setLimitApp("other"); FlowRule flowRule2 = new FlowRule(); flowRule2.setResource(RESOURCE_NAME); flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule2.setCount(1); flowRule2.setLimitApp("app2"); FlowRuleManager.loadRules(Arrays.asList(flowRule, flowRule2)); ContextUtil.enter("node1", "app1"); Entry e = null; try { e = SphU.entry(RESOURCE_NAME); fail("Should had failed"); } catch (BlockException e1) { e1.printStackTrace(); } assertTrue(e == null); ContextUtil.exit(); ContextUtil.enter("node1", "app2"); e = null; try { e = SphU.entry(RESOURCE_NAME); } catch (BlockException e1) { fail("Should had failed"); } e.exit(); ContextUtil.exit(); } @Test public void testFlowRule_other() { FlowRule flowRule = new FlowRule(); flowRule.setResource("testOther"); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setLimitApp("other"); FlowRuleManager.loadRules(Arrays.asList(flowRule)); Entry e = null; try { e = SphU.entry("testOther"); } catch (BlockException e1) { e1.printStackTrace();fail("Should had failed"); } if (e != null) { e.exit(); } else { fail("Should had failed"); } } @Test public void testStrategy() { // normal FlowRule flowRule = new FlowRule(); flowRule.setResource("testStrategy"); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRuleManager.loadRules(Arrays.asList(flowRule)); ContextUtil.enter("testStrategy"); Entry e = null; try { e = SphU.entry("testStrategy"); fail("Should had failed"); } catch (BlockException e1) { e1.printStackTrace(); } ContextUtil.exit(); flowRule = new FlowRule(); flowRule.setResource("testStrategy"); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); flowRule.setResource("entry2"); FlowRuleManager.loadRules(Arrays.asList(flowRule)); e = null; ContextUtil.enter("entry1"); try { e = SphU.entry("testStrategy"); } catch (BlockException e1) { e1.printStackTrace(); fail("Should had failed"); } e.exit(); ContextUtil.exit(); } @Test public void testStrategy_chain() { FlowRule flowRule = new FlowRule(); flowRule.setResource("entry2"); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); flowRule.setRefResource("entry1"); FlowRuleManager.loadRules(Arrays.asList(flowRule)); Entry e = null; ContextUtil.enter("entry1"); try { e = SphU.entry("entry2"); fail("Should had failed"); } catch (BlockException e1) { e1.printStackTrace(); } ContextUtil.exit(); e = null; ContextUtil.enter("entry3"); try { e = SphU.entry("entry2"); } catch (BlockException e1) { fail("Should had failed"); } e.exit(); ContextUtil.exit(); } private void verifyFlow(String resource, boolean shouldPass) { Entry e = null; try { e = SphU.entry(resource); assertTrue(shouldPass); } catch (BlockException e1) { assertFalse(shouldPass); } finally { if (e != null) { e.exit(); } } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleCheckerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.Arrays; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author Eric Zhao */ public class FlowRuleCheckerTest { @Test public void testDefaultLimitAppFlowSelectNode() { DefaultNode node = mock(DefaultNode.class); ClusterNode cn = mock(ClusterNode.class); when(node.getClusterNode()).thenReturn(cn); Context context = mock(Context.class); // limitApp: default FlowRule rule = new FlowRule("testDefaultLimitAppFlowSelectNode").setCount(1); assertEquals(cn, FlowRuleChecker.selectNodeByRequesterAndStrategy(rule, context, node)); } @Test public void testCustomOriginFlowSelectNode() { String origin = "appA"; String limitAppB = "appB"; DefaultNode node = mock(DefaultNode.class); DefaultNode originNode = mock(DefaultNode.class); ClusterNode cn = mock(ClusterNode.class); when(node.getClusterNode()).thenReturn(cn); Context context = mock(Context.class); when(context.getOrigin()).thenReturn(origin); when(context.getOriginNode()).thenReturn(originNode); FlowRule rule = new FlowRule("testCustomOriginFlowSelectNode").setCount(1); rule.setLimitApp(origin); // Origin matches, return the origin node. assertEquals(originNode, FlowRuleChecker.selectNodeByRequesterAndStrategy(rule, context, node)); rule.setLimitApp(limitAppB); // Origin mismatch, no node found. assertNull(FlowRuleChecker.selectNodeByRequesterAndStrategy(rule, context, node)); } @Test public void testOtherOriginFlowSelectNode() { String originA = "appA"; String originB = "appB"; DefaultNode node = mock(DefaultNode.class); DefaultNode originNode = mock(DefaultNode.class); ClusterNode cn = mock(ClusterNode.class); when(node.getClusterNode()).thenReturn(cn); Context context = mock(Context.class); when(context.getOriginNode()).thenReturn(originNode); FlowRule ruleA = new FlowRule("testOtherOriginFlowSelectNode").setCount(1); ruleA.setLimitApp(originA); FlowRule ruleB = new FlowRule("testOtherOriginFlowSelectNode").setCount(2); ruleB.setLimitApp(RuleConstant.LIMIT_APP_OTHER); FlowRuleManager.loadRules(Arrays.asList(ruleA, ruleB)); // Origin matches other, return the origin node. when(context.getOrigin()).thenReturn(originB); assertEquals(originNode, FlowRuleChecker.selectNodeByRequesterAndStrategy(ruleB, context, node)); // Origin matches limitApp of an existing rule, so no nodes are selected. when(context.getOrigin()).thenReturn(originA); assertNull(FlowRuleChecker.selectNodeByRequesterAndStrategy(ruleB, context, node)); } @Test public void testSelectNodeForEmptyReference() { DefaultNode node = mock(DefaultNode.class); Context context = mock(Context.class); FlowRule rule = new FlowRule("testSelectNodeForEmptyReference") .setCount(1) .setStrategy(RuleConstant.STRATEGY_CHAIN); assertNull(FlowRuleChecker.selectReferenceNode(rule, context, node)); } @Test public void testSelectNodeForRelateReference() { String refResource = "testSelectNodeForRelateReference_refResource"; DefaultNode node = mock(DefaultNode.class); ClusterNode refCn = mock(ClusterNode.class); ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(refResource, EntryType.IN), refCn); Context context = mock(Context.class); FlowRule rule = new FlowRule("testSelectNodeForRelateReference") .setCount(1) .setStrategy(RuleConstant.STRATEGY_RELATE) .setRefResource(refResource); assertEquals(refCn, FlowRuleChecker.selectReferenceNode(rule, context, node)); } @Test public void testSelectReferenceNodeForContextEntrance() { String contextName = "good_context"; DefaultNode node = mock(DefaultNode.class); Context context = mock(Context.class); FlowRule rule = new FlowRule("testSelectReferenceNodeForContextEntrance") .setCount(1) .setStrategy(RuleConstant.STRATEGY_CHAIN) .setRefResource(contextName); when(context.getName()).thenReturn(contextName); assertEquals(node, FlowRuleChecker.selectReferenceNode(rule, context, node)); when(context.getName()).thenReturn("other_context"); assertNull(FlowRuleChecker.selectReferenceNode(rule, context, node)); } @Test public void testPassCheckNullLimitApp() { FlowRule rule = new FlowRule("abc").setCount(1); rule.setLimitApp(null); FlowRuleChecker checker = new FlowRuleChecker(); assertTrue(checker.canPassCheck(rule, null, null, 1)); } @Test public void testPassCheckSelectEmptyNodeSuccess() { FlowRule rule = new FlowRule("abc").setCount(1); rule.setLimitApp("abc"); DefaultNode node = mock(DefaultNode.class); Context context = mock(Context.class); when(context.getOrigin()).thenReturn("def"); FlowRuleChecker checker = new FlowRuleChecker(); assertTrue(checker.canPassCheck(rule, context, node, 1)); } @Before public void setUp() throws Exception { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } @After public void tearDown() throws Exception { FlowRuleManager.loadRules(null); ClusterBuilderSlot.getClusterNodeMap().clear(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparatorTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class FlowRuleComparatorTest { @Test public void testFlowRuleComparator() { FlowRule ruleA = new FlowRule("abc") .setCount(10); ruleA.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); FlowRule ruleB = new FlowRule("abc"); ruleB.setLimitApp("originA"); FlowRule ruleC = new FlowRule("abc"); ruleC.setLimitApp("originB"); FlowRule ruleD = new FlowRule("abc"); ruleD.setLimitApp(RuleConstant.LIMIT_APP_OTHER); FlowRule ruleE = new FlowRule("abc") .setCount(20); ruleE.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); List list = Arrays.asList(ruleA, ruleB, ruleC, ruleD, ruleE); FlowRuleComparator comparator = new FlowRuleComparator(); Collections.sort(list, comparator); List expected = Arrays.asList(ruleB, ruleC, ruleD, ruleA, ruleE); assertOrderEqual(expected.size(), expected, list); } private void assertOrderEqual(int size, List expected, List actual) { for (int i = 0; i < size; i++) { assertEquals(expected.get(i), actual.get(i)); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; /** * @author Weihua */ public class FlowRuleManagerTest { public static final List STATIC_RULES_1 = new ArrayList(); public static final List STATIC_RULES_2 = new ArrayList(); static { FlowRule first = new FlowRule(); first.setResource("/a/b/c"); first.setCount(100); STATIC_RULES_1.add(first); FlowRule second = new FlowRule(); second.setResource("/a/b/c"); second.setCount(200); STATIC_RULES_2.add(second); } @Test public void testLoadAndGetRules() throws InterruptedException{ FlowRuleManager.loadRules(STATIC_RULES_1); assertEquals(1, FlowRuleManager.getRules().size()); // the initial size final CountDownLatch latchStart = new CountDownLatch(1); final CountDownLatch latchEnd = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { try { latchStart.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { return; } for(int i = 0; i < 10000; i++){ //to guarantee that they're different and change happens FlowRuleManager.loadRules(i % 2 == 0 ? STATIC_RULES_2 : STATIC_RULES_1); } latchEnd.countDown(); } }).start(); latchStart.countDown(); for (int i = 0; i < 10000; i++) { //The initial size is 1, and the size after updating should also be 1, //if the actual size is 0, that must be called after clear(), // but before putAll() in FlowPropertyListener.configUpdate assertEquals(1, FlowRuleManager.getRules().size()); } latchEnd.await(10, TimeUnit.SECONDS); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.Collections; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.*; /** * @author Eric Zhao */ public class FlowSlotTest { @Before public void setUp() { ContextTestUtil.cleanUpContext(); FlowRuleManager.loadRules(null); } @After public void tearDown() { ContextTestUtil.cleanUpContext(); FlowRuleManager.loadRules(null); } @Test @SuppressWarnings("unchecked") public void testCheckFlowPass() throws Exception { FlowRuleChecker checker = mock(FlowRuleChecker.class); FlowSlot flowSlot = new FlowSlot(checker); Context context = mock(Context.class); DefaultNode node = mock(DefaultNode.class); doCallRealMethod().when(checker).checkFlow(any(Function.class), any(ResourceWrapper.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean()); String resA = "resAK"; String resB = "resBK"; FlowRule rule1 = new FlowRule(resA).setCount(10); FlowRule rule2 = new FlowRule(resB).setCount(10); // Here we only load rules for resA. FlowRuleManager.loadRules(Collections.singletonList(rule1)); when(checker.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(true); when(checker.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(false); flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1, false); } @Test(expected = FlowException.class) @SuppressWarnings("unchecked") public void testCheckFlowBlock() throws Exception { FlowRuleChecker checker = mock(FlowRuleChecker.class); FlowSlot flowSlot = new FlowSlot(checker); Context context = mock(Context.class); DefaultNode node = mock(DefaultNode.class); doCallRealMethod().when(checker).checkFlow(any(Function.class), any(ResourceWrapper.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean()); String resA = "resAK"; FlowRule rule = new FlowRule(resA).setCount(10); FlowRuleManager.loadRules(Collections.singletonList(rule)); when(checker.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(false); flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultControllerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow.controller; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author Eric Zhao */ public class DefaultControllerTest { @Test public void testCanPassForQps() { double threshold = 10; TrafficShapingController controller = new DefaultController(threshold, RuleConstant.FLOW_GRADE_QPS); Node node = mock(Node.class); when(node.passQps()).thenReturn(threshold - 1) .thenReturn(threshold); assertTrue(controller.canPass(node, 1)); assertFalse(controller.canPass(node, 1)); } @Test public void testCanPassForThreadCount() { int threshold = 8; TrafficShapingController controller = new DefaultController(threshold, RuleConstant.FLOW_GRADE_THREAD); Node node = mock(Node.class); when(node.curThreadNum()).thenReturn(threshold - 1) .thenReturn(threshold); assertTrue(controller.canPass(node, 1)); assertFalse(controller.canPass(node, 1)); } @Test public void testCanPassForQpsMultiThread() { } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/ThrottlingControllerTest.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; /** * @author Eric Zhao * @author jialiang.linjl */ public class ThrottlingControllerTest { @Test public void testThrottlingControllerNormal() throws InterruptedException { ThrottlingController paceController = new ThrottlingController(500, 10d); Node node = mock(Node.class); long start = TimeUtil.currentTimeMillis(); for (int i = 0; i < 6; i++) { assertTrue(paceController.canPass(node, 1)); } long end = TimeUtil.currentTimeMillis(); assertTrue((end - start) > 400); } @Test public void testThrottlingControllerQueueTimeout() throws InterruptedException { final ThrottlingController paceController = new ThrottlingController(500, 10d); final Node node = mock(Node.class); final AtomicInteger passCount = new AtomicInteger(); final AtomicInteger blockCount = new AtomicInteger(); final CountDownLatch countDown = new CountDownLatch(1); final AtomicInteger done = new AtomicInteger(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { boolean pass = paceController.canPass(node, 1); if (pass) { passCount.incrementAndGet(); } else { blockCount.incrementAndGet(); } done.incrementAndGet(); if (done.get() >= 10) { countDown.countDown(); } } }, "Thread-TestThrottlingControllerQueueTimeout-" + i); thread.start(); } countDown.await(); System.out.println("pass: " + passCount.get()); System.out.println("block: " + blockCount.get()); System.out.println("done: " + done.get()); assertTrue(blockCount.get() > 0); } @Test public void testThrottlingControllerZeroThreshold() throws InterruptedException { ThrottlingController paceController = new ThrottlingController(500, 0d); Node node = mock(Node.class); for (int i = 0; i < 2; i++) { assertFalse(paceController.canPass(node, 1)); assertTrue(paceController.canPass(node, 0)); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpControllerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.controller; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import org.mockito.MockedStatic; /** * @author jialiang.linjl */ public class WarmUpControllerTest extends AbstractTimeBasedTest { @Test public void testWarmUp() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { WarmUpController warmupController = new WarmUpController(10, 10, 3); setCurrentMillis(mocked, System.currentTimeMillis()); Node node = mock(Node.class); when(node.passQps()).thenReturn(8d); when(node.previousPassQps()).thenReturn(1d); assertFalse(warmupController.canPass(node, 1)); when(node.passQps()).thenReturn(1d); when(node.previousPassQps()).thenReturn(1d); assertTrue(warmupController.canPass(node, 1)); when(node.previousPassQps()).thenReturn(10d); for (int i = 0; i < 100; i++) { sleep(mocked, 100); warmupController.canPass(node, 1); } when(node.passQps()).thenReturn(8d); assertTrue(warmupController.canPass(node, 1)); when(node.passQps()).thenReturn(10d); assertFalse(warmupController.canPass(node, 1)); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterControllerTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow.controller; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author CarpenterLee */ public class WarmUpRateLimiterControllerTest { @Test public void testPace() throws InterruptedException { WarmUpRateLimiterController controller = new WarmUpRateLimiterController(10, 10, 1000, 3); Node node = mock(Node.class); when(node.passQps()).thenReturn(100d); when(node.previousPassQps()).thenReturn(100d); assertTrue(controller.canPass(node, 1)); // Easily fail in single request testing, so we increase it to 10 requests and test the average time long start = System.currentTimeMillis(); int requests = 10; for (int i = 0; i < requests; i++) { assertTrue(controller.canPass(node, 1)); } float cost = (System.currentTimeMillis() - start) / 1.0f / requests; assertTrue(Math.abs(cost - 100) < 10); } @Test public void testPaceCanNotPass() throws InterruptedException { WarmUpRateLimiterController controller = new WarmUpRateLimiterController(10, 10, 10, 3); Node node = mock(Node.class); when(node.passQps()).thenReturn(100d); when(node.previousPassQps()).thenReturn(100d); assertTrue(controller.canPass(node, 1)); assertFalse(controller.canPass(node, 1)); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/tokenbucket/TokenBucketTest.java ================================================ /* * Copyright 1999-2023 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.tokenbucket; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.MockedStatic; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; /** * @author LearningGp */ public class TokenBucketTest extends AbstractTimeBasedTest { private static ThreadPoolExecutor threadPoolExecutor; @BeforeClass public static void beforeClass() throws Exception { threadPoolExecutor = new ThreadPoolExecutor(64, 64, 0, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("sentinel-token-bucket-test", true), new ThreadPoolExecutor.AbortPolicy()); } @AfterClass public static void afterClass() throws Exception { threadPoolExecutor.shutdownNow(); } @Test public void testForDefaultTokenBucket() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { long unitProduceNum = 1; long maxTokenNum = 2; long intervalInMs = 1000; long testStart = System.currentTimeMillis(); setCurrentMillis(mocked, testStart); DefaultTokenBucket defaultTokenBucket = new DefaultTokenBucket(unitProduceNum, maxTokenNum, intervalInMs); assertTrue(defaultTokenBucket.tryConsume(1)); assertFalse(defaultTokenBucket.tryConsume(1)); DefaultTokenBucket defaultTokenBucketFullStart = new DefaultTokenBucket(unitProduceNum, maxTokenNum, true, intervalInMs); assertTrue(defaultTokenBucketFullStart.tryConsume(2)); assertFalse(defaultTokenBucketFullStart.tryConsume(1)); sleep(mocked, 1000); assertTrue(defaultTokenBucket.tryConsume(1)); assertFalse(defaultTokenBucket.tryConsume(1)); sleep(mocked, 1000); assertTrue(defaultTokenBucketFullStart.tryConsume(2)); assertFalse(defaultTokenBucketFullStart.tryConsume(1)); } } @Test public void testForStrictTokenBucket() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { long unitProduceNum = 5; long maxTokenNum = 10; long intervalInMs = 1000; final int n = 64; long testStart = System.currentTimeMillis(); setCurrentMillis(mocked, testStart); final AtomicLong passNum = new AtomicLong(); final AtomicLong passNumFullStart = new AtomicLong(); final CountDownLatch countDownLatch = new CountDownLatch(n); final CountDownLatch countDownLatchFullStart = new CountDownLatch(n); final StrictTokenBucket strictTokenBucket = new StrictTokenBucket(unitProduceNum, maxTokenNum, intervalInMs); final StrictTokenBucket strictTokenBucketFullStart = new StrictTokenBucket(unitProduceNum, maxTokenNum, true, intervalInMs); for (int i = 0; i < n; i++) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { if (strictTokenBucket.tryConsume(1)) { passNum.incrementAndGet(); } countDownLatch.countDown(); } }); threadPoolExecutor.execute(new Runnable() { @Override public void run() { if (strictTokenBucketFullStart.tryConsume(1)) { passNumFullStart.incrementAndGet(); } countDownLatchFullStart.countDown(); } }); } countDownLatch.await(); countDownLatchFullStart.await(); assertEquals(5, passNum.longValue()); assertEquals(10, passNumFullStart.longValue()); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/system/SystemGuardIntegrationTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.system; /** * @author jialiang.linjl */ public class SystemGuardIntegrationTest { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/system/SystemRuleTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.system; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collections; import org.junit.Test; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * @author jialiang.linjl */ public class SystemRuleTest { @Test public void testSystemRule_load() { SystemRule systemRule = new SystemRule(); systemRule.setAvgRt(4000L); SystemRuleManager.loadRules(Collections.singletonList(systemRule)); } @Test public void testSystemRule_avgRt() throws BlockException { SystemRule systemRule = new SystemRule(); systemRule.setAvgRt(4L); Context context = mock(Context.class); DefaultNode node = mock(DefaultNode.class); ClusterNode cn = mock(ClusterNode.class); when(context.getOrigin()).thenReturn(""); when(node.getClusterNode()).thenReturn(cn); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterNodeBuilderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.clusterbuilder; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import org.junit.Test; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; /** * @author jialiang.linjl */ public class ClusterNodeBuilderTest { @Test public void clusterNodeBuilder_normal() throws Exception { ContextUtil.enter("entry1", "caller1"); Entry nodeA = SphU.entry("nodeA"); Node curNode = nodeA.getCurNode(); assertSame(curNode.getClass(), DefaultNode.class); DefaultNode dN = (DefaultNode)curNode; assertTrue(dN.getClusterNode().getOriginCountMap().containsKey("caller1")); assertSame(nodeA.getOriginNode(), dN.getClusterNode().getOrCreateOriginNode("caller1")); if (nodeA != null) { nodeA.exit(); } ContextUtil.exit(); ContextUtil.enter("entry4", "caller2"); nodeA = SphU.entry("nodeA"); curNode = nodeA.getCurNode(); assertSame(curNode.getClass(), DefaultNode.class); DefaultNode dN1 = (DefaultNode)curNode; assertTrue(dN1.getClusterNode().getOriginCountMap().containsKey("caller2")); assertNotSame(dN1, dN); if (nodeA != null) { nodeA.exit(); } ContextUtil.exit(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java ================================================ package com.alibaba.csp.sentinel.slots.logger; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.log.RecordLog; import org.hamcrest.io.FileMatchers; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; /** * @author Carpenter Lee */ public class EagleEyeLogUtilTest { @Test public void testWriteLog() throws Exception { EagleEyeLogUtil.log("resourceName", "BlockException", "app1", "origin", 1L,1); final File file = new File(LogBase.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); await().timeout(2, TimeUnit.SECONDS) .until(new Callable() { @Override public File call() throws Exception { return file; } }, FileMatchers.anExistingFile()); } //Change LogBase It is not not work when integration Testing //Because LogBase.LOG_DIR can be just static init for once and it will not be changed //@Test public void testChangeLogBase() throws Exception { String userHome = System.getProperty("user.home"); String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis(); System.setProperty(LogBase.LOG_DIR, newLogBase); EagleEyeLogUtil.log("resourceName", "BlockException", "app1", "origin", 2L,1); final File file = new File(LogBase.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); await().timeout(2, TimeUnit.SECONDS) .until(new Callable() { @Override public File call() throws Exception { return file; } }, FileMatchers.anExistingFile()); Assert.assertTrue(file.getAbsolutePath().startsWith(newLogBase)); deleteLogDir(new File(LogBase.getLogBaseDir())); } private void deleteLogDir(File logDirFile) { if (logDirFile != null && logDirFile.isDirectory()) { if (logDirFile.listFiles() != null) { for (File file : logDirFile.listFiles()) { file.delete(); } } logDirFile.delete(); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.nodeselector; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import org.junit.Test; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; /** * @author jialiang.linjl * @author Eric Zhao */ public class NodeSelectorTest { @Test public void testSingleEntrance() throws Exception { final String contextName = "entry_SingleEntrance"; ContextUtil.enter(contextName); EntranceNode entranceNode = null; for (Node node : Constants.ROOT.getChildList()) { entranceNode = (EntranceNode)node; if (entranceNode.getId().getName().equals(contextName)) { break; } else { System.out.println("Single entry: " + entranceNode.getId().getName()); } } assertNotNull(entranceNode); assertTrue(entranceNode.getId().getName().equalsIgnoreCase(contextName)); final String resName = "nodeA"; Entry nodeA = SphU.entry(resName); assertNotNull(ContextUtil.getContext().getCurNode()); assertEquals(resName, ((DefaultNode)ContextUtil.getContext().getCurNode()).getId().getName()); boolean hasNode = false; for (Node node : entranceNode.getChildList()) { if (((DefaultNode)node).getId().getName().equals(resName)) { hasNode = true; } } assertTrue(hasNode); if (nodeA != null) { nodeA.exit(); } ContextUtil.exit(); } @Test public void testMultipleEntrance() throws Exception { final String firstEntry = "entry_multiple_one"; final String anotherEntry = "entry_multiple_another"; final String resName = "nodeA"; Node firstNode, anotherNode; ContextUtil.enter(firstEntry); Entry nodeA = SphU.entry(resName); firstNode = ContextUtil.getContext().getCurNode(); if (nodeA != null) { nodeA.exit(); } ContextUtil.exit(); ContextUtil.enter(anotherEntry); nodeA = SphU.entry(resName); anotherNode = ContextUtil.getContext().getCurNode(); if (nodeA != null) { nodeA.exit(); } assertNotSame(firstNode, anotherNode); for (Node node : Constants.ROOT.getChildList()) { EntranceNode firstEntrance = (EntranceNode)node; if (firstEntrance.getId().getName().equals(firstEntry)) { assertEquals(1, firstEntrance.getChildList().size()); for (Node child : firstEntrance.getChildList()) { assertEquals(resName, ((DefaultNode)child).getId().getName()); } } else if (firstEntrance.getId().getName().equals(anotherEntry)) { assertEquals(1, firstEntrance.getChildList().size()); for (Node child : firstEntrance.getChildList()) { assertEquals(resName, ((DefaultNode)child).getId().getName()); } } else { System.out.println("Multiple entries: " + firstEntrance.getId().getName()); } } ContextUtil.exit(); } //@Test public void testMultipleLayer() throws Exception { // TODO: fix this ContextUtil.enter("entry1", "appA"); Entry nodeA = SphU.entry("nodeA"); assertSame(ContextUtil.getContext().getCurEntry(), nodeA); DefaultNode dnA = (DefaultNode)nodeA.getCurNode(); assertNotNull(dnA); assertSame("nodeA", dnA.getId().getName()); Entry nodeB = SphU.entry("nodeB"); assertSame(ContextUtil.getContext().getCurEntry(), nodeB); DefaultNode dnB = (DefaultNode)nodeB.getCurNode(); assertNotNull(dnB); assertTrue(dnA.getChildList().contains(dnB)); Entry nodeC = SphU.entry("nodeC"); assertSame(ContextUtil.getContext().getCurEntry(), nodeC); DefaultNode dnC = (DefaultNode)nodeC.getCurNode(); assertNotNull(dnC); assertTrue(dnB.getChildList().contains(dnC)); if (nodeC != null) { nodeC.exit(); } assertSame(ContextUtil.getContext().getCurEntry(), nodeB); if (nodeB != null) { nodeB.exit(); } assertSame(ContextUtil.getContext().getCurEntry(), nodeA); if (nodeA != null) { nodeA.exit(); } assertNull(ContextUtil.getContext().getCurEntry()); ContextUtil.exit(); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.base; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import org.mockito.MockedStatic; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class LeapArrayTest extends AbstractTimeBasedTest { @Test public void testGetValidHead() { try (MockedStatic mocked = super.mockTimeUtil()) { int windowLengthInMs = 100; int intervalInMs = 1000; int sampleCount = intervalInMs / windowLengthInMs; LeapArray leapArray = new LeapArray(sampleCount, intervalInMs) { @Override public AtomicInteger newEmptyBucket(long time) { return new AtomicInteger(0); } @Override protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { windowWrap.resetTo(startTime); windowWrap.value().set(0); return windowWrap; } }; WindowWrap expected1 = leapArray.currentWindow(); expected1.value().addAndGet(1); sleep(mocked, windowLengthInMs); WindowWrap expected2 = leapArray.currentWindow(); expected2.value().addAndGet(2); for (int i = 0; i < sampleCount - 2; i++) { sleep(mocked, windowLengthInMs); leapArray.currentWindow().value().addAndGet(i + 3); } assertSame(expected1, leapArray.getValidHead()); sleep(mocked, windowLengthInMs); assertSame(expected2, leapArray.getValidHead()); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetricTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.util.function.Predicate; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test cases for {@link ArrayMetric}. * * @author Eric Zhao */ public class ArrayMetricTest { private final int windowLengthInMs = 500; @Test public void testOperateArrayMetric() { BucketLeapArray leapArray = mock(BucketLeapArray.class); final WindowWrap windowWrap = new WindowWrap(windowLengthInMs, 0, new MetricBucket()); when(leapArray.currentWindow()).thenReturn(windowWrap); when(leapArray.values()).thenReturn(new ArrayList() {{ add(windowWrap.value()); }}); ArrayMetric metric = new ArrayMetric(leapArray); final int expectedPass = 9; final int expectedBlock = 2; final int expectedSuccess = 9; final int expectedException = 6; final int expectedRt = 21; metric.addRT(expectedRt); for (int i = 0; i < expectedPass; i++) { metric.addPass(1); } for (int i = 0; i < expectedBlock; i++) { metric.addBlock(1); } for (int i = 0; i < expectedSuccess; i++) { metric.addSuccess(1); } for (int i = 0; i < expectedException; i++) { metric.addException(1); } assertEquals(expectedPass, metric.pass()); assertEquals(expectedBlock, metric.block()); assertEquals(expectedSuccess, metric.success()); assertEquals(expectedException, metric.exception()); assertEquals(expectedRt, metric.rt()); } @Test public void testGetMetricDetailsOnCondition() { BucketLeapArray leapArray = mock(BucketLeapArray.class); // Mock interval=2s, sampleCount=2 final WindowWrap w1 = new WindowWrap<>(windowLengthInMs, 500, new MetricBucket().add(MetricEvent.PASS, 1)); final WindowWrap w2 = new WindowWrap<>(windowLengthInMs, 1000, new MetricBucket().add(MetricEvent.PASS, 2)); final WindowWrap w3 = new WindowWrap<>(windowLengthInMs, 1500, new MetricBucket().add(MetricEvent.PASS, 3)); final WindowWrap w4 = new WindowWrap<>(windowLengthInMs, 2000, new MetricBucket().add(MetricEvent.PASS, 4)); List> buckets = Arrays.asList(w1, w2, w3, w4); when(leapArray.currentWindow()).thenReturn(w4); when(leapArray.list()).thenReturn(buckets); ArrayMetric metric = new ArrayMetric(leapArray); // Empty condition -> retrieve all assertEquals(4, metric.detailsOnCondition(null).size()); // Normal condition List metricNodes = metric.detailsOnCondition(new Predicate() { @Override public boolean test(Long t) { return t >= 1500; } }); assertEquals(2, metricNodes.size()); assertEquals(3, metricNodes.get(0).getPassQps()); assertEquals(4, metricNodes.get(1).getPassQps()); // Future condition metricNodes = metric.detailsOnCondition(new Predicate() { @Override public boolean test(Long t) { return t >= 2500; } }); assertEquals(0, metricNodes.size()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArrayTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link BucketLeapArray}. * * @author Eric Zhao */ public class BucketLeapArrayTest { private final int windowLengthInMs = 1000; private final int intervalInSec = 2; private final int intervalInMs = intervalInSec * 1000; private final int sampleCount = intervalInMs / windowLengthInMs; @Test public void testNewWindow() { BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long time = TimeUtil.currentTimeMillis(); WindowWrap window = leapArray.currentWindow(time); assertEquals(window.windowLength(), windowLengthInMs); assertEquals(window.windowStart(), time - time % windowLengthInMs); assertNotNull(window.value()); assertEquals(0L, window.value().pass()); } @Test public void testLeapArrayWindowStart() { BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long firstTime = TimeUtil.currentTimeMillis(); long previousWindowStart = firstTime - firstTime % windowLengthInMs; WindowWrap window = leapArray.currentWindow(firstTime); assertEquals(windowLengthInMs, window.windowLength()); assertEquals(previousWindowStart, window.windowStart()); } @Test public void testWindowAfterOneInterval() { BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long firstTime = TimeUtil.currentTimeMillis(); long previousWindowStart = firstTime - firstTime % windowLengthInMs; WindowWrap window = leapArray.currentWindow(previousWindowStart); assertEquals(windowLengthInMs, window.windowLength()); assertEquals(previousWindowStart, window.windowStart()); MetricBucket currentWindow = window.value(); assertNotNull(currentWindow); currentWindow.addPass(1); currentWindow.addBlock(1); assertEquals(1L, currentWindow.pass()); assertEquals(1L, currentWindow.block()); long middleTime = previousWindowStart + windowLengthInMs / 2; window = leapArray.currentWindow(middleTime); assertEquals(previousWindowStart, window.windowStart()); MetricBucket middleWindow = window.value(); middleWindow.addPass(1); assertSame(currentWindow, middleWindow); assertEquals(2L, middleWindow.pass()); assertEquals(1L, middleWindow.block()); long nextTime = middleTime + windowLengthInMs / 2; window = leapArray.currentWindow(nextTime); assertEquals(windowLengthInMs, window.windowLength()); assertEquals(windowLengthInMs, window.windowStart() - previousWindowStart); currentWindow = window.value(); assertNotNull(currentWindow); assertEquals(0L, currentWindow.pass()); assertEquals(0L, currentWindow.block()); } @Deprecated public void testWindowDeprecatedRefresh() { BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); final int len = sampleCount; long firstTime = TimeUtil.currentTimeMillis(); List> firstIterWindowList = new ArrayList>(len); for (int i = 0; i < len; i++) { WindowWrap w = leapArray.currentWindow(firstTime + windowLengthInMs * i); w.value().addPass(1); firstIterWindowList.add(i, w); } for (int i = len; i < len * 2; i++) { WindowWrap w = leapArray.currentWindow(firstTime + windowLengthInMs * i); assertNotSame(w, firstIterWindowList.get(i - len)); } } @Test public void testMultiThreadUpdateEmptyWindow() throws Exception { final long time = TimeUtil.currentTimeMillis(); final int nThreads = 16; final BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); final CountDownLatch latch = new CountDownLatch(nThreads); Runnable task = new Runnable() { @Override public void run() { leapArray.currentWindow(time).value().addPass(1); latch.countDown(); } }; for (int i = 0; i < nThreads; i++) { new Thread(task).start(); } latch.await(); assertEquals(nThreads, leapArray.currentWindow(time).value().pass()); } @Test public void testGetPreviousWindow() { BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long time = TimeUtil.currentTimeMillis(); WindowWrap previousWindow = leapArray.currentWindow(time); assertNull(leapArray.getPreviousWindow(time)); long nextTime = time + windowLengthInMs; assertSame(previousWindow, leapArray.getPreviousWindow(nextTime)); long longTime = time + 11 * windowLengthInMs; assertNull(leapArray.getPreviousWindow(longTime)); } @Test public void testListWindowsResetOld() throws Exception { final int windowLengthInMs = 100; final int intervalInMs = 1000; final int sampleCount = intervalInMs / windowLengthInMs; BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long time = TimeUtil.currentTimeMillis(); Set> windowWraps = new HashSet>(); windowWraps.add(leapArray.currentWindow(time)); windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); List> list = leapArray.list(); for (WindowWrap wrap : list) { assertTrue(windowWraps.contains(wrap)); } Thread.sleep(windowLengthInMs + intervalInMs); // This will replace the deprecated bucket, so all deprecated buckets will be reset. leapArray.currentWindow(time + windowLengthInMs + intervalInMs).value().addPass(1); assertEquals(1, leapArray.list().size()); } @Test public void testListWindowsNewBucket() throws Exception { final int windowLengthInMs = 100; final int intervalInSec = 1; final int intervalInMs = intervalInSec * 1000; final int sampleCount = intervalInMs / windowLengthInMs; BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); long time = TimeUtil.currentTimeMillis(); Set> windowWraps = new HashSet>(); windowWraps.add(leapArray.currentWindow(time)); windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); Thread.sleep(intervalInMs + windowLengthInMs * 3); List> list = leapArray.list(); for (WindowWrap wrap : list) { assertTrue(windowWraps.contains(wrap)); } // This won't hit deprecated bucket, so no deprecated buckets will be reset. // But deprecated buckets can be filtered when collecting list. leapArray.currentWindow(TimeUtil.currentTimeMillis()).value().addPass(1); assertEquals(1, leapArray.list().size()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/FutureBucketLeapArrayTest.java ================================================ package com.alibaba.csp.sentinel.slots.statistic.metric; import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.FutureBucketLeapArray; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * Test cases for {@link FutureBucketLeapArray}. * * @author jialiang.linjl */ public class FutureBucketLeapArrayTest { private final int windowLengthInMs = 200; private final int intervalInSec = 2; private final int intervalInMs = intervalInSec * 1000; private final int sampleCount = intervalInMs / windowLengthInMs; @Test public void testFutureMetricLeapArray() { FutureBucketLeapArray array = new FutureBucketLeapArray(sampleCount, intervalInMs); long currentTime = TimeUtil.currentTimeMillis(); for (int i = 0; i < intervalInSec * 1000; i = i + windowLengthInMs) { array.currentWindow(i + currentTime).value().addPass(1); assertEquals(array.values(i + currentTime).size(), 0); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/OccupiableBucketLeapArrayTest.java ================================================ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.List; import java.util.concurrent.CountDownLatch; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray; import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.Test; import org.mockito.MockedStatic; import static org.junit.Assert.assertEquals; /** * Test cases for {@link OccupiableBucketLeapArray}. * * @author jialiang.linjl */ public class OccupiableBucketLeapArrayTest extends AbstractTimeBasedTest { private final int windowLengthInMs = 200; private final int intervalInSec = 2; private final int intervalInMs = intervalInSec * 1000; private final int sampleCount = intervalInMs / windowLengthInMs; @Test public void testNewWindow() { try (MockedStatic mocked = super.mockTimeUtil()) { long currentTime = System.currentTimeMillis(); setCurrentMillis(mocked, currentTime); OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); WindowWrap currentWindow = leapArray.currentWindow(currentTime); currentWindow.value().addPass(1); assertEquals(currentWindow.value().pass(), 1L); leapArray.addWaiting(currentTime + windowLengthInMs, 1); assertEquals(leapArray.currentWaiting(), 1); assertEquals(currentWindow.value().pass(), 1L); } } @Test public void testWindowInOneInterval() { try (MockedStatic mocked = super.mockTimeUtil()) { OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); long currentTime = System.currentTimeMillis(); setCurrentMillis(mocked, currentTime); WindowWrap currentWindow = leapArray.currentWindow(currentTime); currentWindow.value().addPass(1); assertEquals(currentWindow.value().pass(), 1L); leapArray.addWaiting(currentTime + windowLengthInMs, 2); assertEquals(leapArray.currentWaiting(), 2); assertEquals(currentWindow.value().pass(), 1L); leapArray.currentWindow(currentTime + windowLengthInMs); List values = leapArray.values(currentTime + windowLengthInMs); assertEquals(values.size(), 2); long sum = 0; for (MetricBucket bucket : values) { sum += bucket.pass(); } assertEquals(sum, 3); } } @Test public void testMultiThreadUpdateEmptyWindow() throws Exception { try (MockedStatic mocked = super.mockTimeUtil()) { final long time = System.currentTimeMillis(); setCurrentMillis(mocked, time); final int nThreads = 16; final OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); final CountDownLatch latch = new CountDownLatch(nThreads); Runnable task = new Runnable() { @Override public void run() { leapArray.currentWindow(time).value().addPass(1); leapArray.addWaiting(time + windowLengthInMs, 1); latch.countDown(); } }; for (int i = 0; i < nThreads; i++) { new Thread(task).start(); } latch.await(); assertEquals(nThreads, leapArray.currentWindow(time).value().pass()); assertEquals(nThreads, leapArray.currentWaiting()); leapArray.currentWindow(time + windowLengthInMs); long sum = 0; List values = leapArray.values(time + windowLengthInMs); for (MetricBucket bucket : values) { sum += bucket.pass(); } assertEquals(values.size(), 2); assertEquals(sum, nThreads * 2); } } @Test public void testWindowAfterOneInterval() { try (MockedStatic mocked = super.mockTimeUtil()) { OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); long currentTime = System.currentTimeMillis(); setCurrentMillis(mocked, currentTime); System.out.println(currentTime); for (int i = 0; i < intervalInSec * 1000 / windowLengthInMs; i++) { WindowWrap currentWindow = leapArray.currentWindow(currentTime + i * windowLengthInMs); currentWindow.value().addPass(1); leapArray.addWaiting(currentTime + (i + 1) * windowLengthInMs, 1); System.out.println(currentTime + i * windowLengthInMs); leapArray.debug(currentTime + i * windowLengthInMs); } System.out.println(currentTime + intervalInSec * 1000); List values = leapArray .values(currentTime - currentTime % windowLengthInMs + intervalInSec * 1000); leapArray.debug(currentTime + intervalInSec * 1000); assertEquals(values.size(), intervalInSec * 1000 / windowLengthInMs); long sum = 0; for (MetricBucket bucket : values) { sum += bucket.pass(); } assertEquals(sum, 2 * intervalInSec * 1000 / windowLengthInMs - 1); /** * https://github.com/alibaba/Sentinel/issues/685 * * Here we could not use exactly current time, because the following result is related with the above elapse. * So we use the beginning current time to ensure. */ assertEquals(leapArray.currentWaiting(), 10); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.system; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class SystemRuleManagerTest { @Test public void testLoadInvalidRules() { SystemRule rule1 = new SystemRule(); rule1.setHighestSystemLoad(-0.9d); SystemRule rule2 = new SystemRule(); rule2.setHighestCpuUsage(2.7d); SystemRuleManager.loadRules(Arrays.asList(rule1, rule2)); assertEquals(0, SystemRuleManager.getRules().size()); } @Test public void testLoadAndGetRules() { SystemRule rule1 = new SystemRule(); rule1.setHighestSystemLoad(1.2d); SystemRule rule2 = new SystemRule(); rule2.setMaxThread(17); SystemRule rule3 = new SystemRule(); rule3.setHighestCpuUsage(0.7d); SystemRule rule4 = new SystemRule(); rule4.setQps(1500); SystemRule rule5 = new SystemRule(); rule5.setAvgRt(50); SystemRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3, rule4, rule5)); assertEquals(1.2d, SystemRuleManager.getSystemLoadThreshold(), 0.01); assertEquals(17, SystemRuleManager.getMaxThreadThreshold()); assertEquals(0.7d, SystemRuleManager.getCpuUsageThreshold(), 0.01); assertEquals(1500, SystemRuleManager.getInboundQpsThreshold(), 0.01); assertEquals(50, SystemRuleManager.getRtThreshold()); } @Test public void testLoadDuplicateTypeOfRules() { SystemRule rule1 = new SystemRule(); rule1.setHighestSystemLoad(1.2d); SystemRule rule2 = new SystemRule(); rule2.setHighestSystemLoad(2.3d); SystemRule rule3 = new SystemRule(); rule3.setHighestSystemLoad(3.4d); SystemRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3)); List rules = SystemRuleManager.getRules(); assertEquals(1, rules.size()); assertEquals(1.2d, rules.get(0).getHighestSystemLoad(), 0.01); assertEquals(1.2d, SystemRuleManager.getSystemLoadThreshold(), 0.01); } @Test public void testCheckMaxCpuUsageNotBBR() throws Exception { SystemRule rule1 = new SystemRule(); rule1.setHighestCpuUsage(0d); SystemRuleManager.loadRules(Collections.singletonList(rule1)); // Wait until SystemStatusListener triggered the first CPU usage collecting. Thread.sleep(1500); boolean blocked = false; try { StringResourceWrapper resourceWrapper = new StringResourceWrapper("testCheckMaxCpuUsageNotBBR", EntryType.IN); SystemRuleManager.checkSystem(resourceWrapper, 1); } catch (BlockException ex) { blocked = true; } assertTrue("The entry should be blocked under SystemRule maxCpuUsage=0", blocked); } @Before public void setUp() throws Exception { SystemRuleManager.loadRules(new ArrayList()); } @After public void tearDown() throws Exception { SystemRuleManager.loadRules(new ArrayList()); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/SpiLoaderTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.spi; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; import com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.slots.logger.LogSlot; import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; import com.alibaba.csp.sentinel.slots.system.SystemSlot; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; /** * Test cases for {@link SpiLoader}. * * @author cdfive */ public class SpiLoaderTest { @Before public void setUp() { SpiLoader.resetAndClearAll(); } @Before public void tearDown() { SpiLoader.resetAndClearAll(); } @Test public void testCreateSpiLoader() { SpiLoader slotLoader1 = SpiLoader.of(ProcessorSlot.class); assertNotNull(slotLoader1); SpiLoader slotLoader2 = SpiLoader.of(ProcessorSlot.class); assertNotNull(slotLoader2); assertSame(slotLoader1, slotLoader2); SpiLoader initFuncLoader1 = SpiLoader.of(InitFunc.class); assertNotNull(initFuncLoader1); assertNotSame(slotLoader1, initFuncLoader1); assertNotEquals(slotLoader1, initFuncLoader1); SpiLoader initFuncLoader2 = SpiLoader.of(InitFunc.class); assertNotNull(initFuncLoader2); assertSame(initFuncLoader1, initFuncLoader2); } @Test public void testCreateSpiLoaderNotInterface() { try { SpiLoader.of(SphU.class); fail(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); assertThat(e.getMessage(), containsString("must be interface or abstract class")); } } @Test public void testLoadInstanceList() { SpiLoader spiLoader = SpiLoader.of(ProcessorSlot.class); List slots1 = spiLoader.loadInstanceList(); List slots2 = spiLoader.loadInstanceList(); assertNotSame(slots1, slots2); List> prototypeSlotClasses = new ArrayList<>(2); prototypeSlotClasses.add(NodeSelectorSlot.class); prototypeSlotClasses.add(ClusterBuilderSlot.class); List> singletonSlotClasses = new ArrayList<>(7); singletonSlotClasses.add(LogSlot.class); singletonSlotClasses.add(StatisticSlot.class); singletonSlotClasses.add(AuthoritySlot.class); singletonSlotClasses.add(SystemSlot.class); singletonSlotClasses.add(FlowSlot.class); singletonSlotClasses.add(DegradeSlot.class); singletonSlotClasses.add(DefaultCircuitBreakerSlot.class); for (int i = 0; i < slots1.size(); i++) { ProcessorSlot slot1 = slots1.get(i); ProcessorSlot slot2 = slots2.get(i); assertSame(slot1.getClass(), slot2.getClass()); boolean found = false; for (Class prototypeSlotClass : prototypeSlotClasses) { if (prototypeSlotClass.equals(slot1.getClass())) { found = true; assertTrue(prototypeSlotClass.equals(slot2.getClass())); // Verify prototype function assertNotSame(slot1, slot2); break; } } if (found) { continue; } for (Class singletonSlotClass : singletonSlotClasses) { if (singletonSlotClass.equals(slot1.getClass())) { found = true; assertTrue(singletonSlotClass.equals(slot2.getClass())); // Verify single function assertSame(slot1, slot2); break; } } if (!found) { fail("Should found and not go through here"); } } } @Test public void testLoadInstanceListSorted() { List sortedSlots = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); assertNotNull(sortedSlots); // Total 8 default slot in sentinel-core assertEquals(9, sortedSlots.size()); // Verify the order of slot int index = 0; assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); assertTrue(sortedSlots.get(index++) instanceof LogSlot); assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); assertTrue(sortedSlots.get(index++) instanceof SystemSlot); assertTrue(sortedSlots.get(index++) instanceof FlowSlot); assertTrue(sortedSlots.get(index++) instanceof DefaultCircuitBreakerSlot); assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); } @Test public void testLoadHighestPriorityInstance() { ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadHighestPriorityInstance(); assertNotNull(slot); // NodeSelectorSlot is highest order priority with @Spi(order = -10000) among all slots assertTrue(slot instanceof NodeSelectorSlot); } @Test public void testLoadLowestPriorityInstance() { ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadLowestPriorityInstance(); assertNotNull(slot); // DegradeSlot is lowest order priority with @Spi(order = -1000) among all slots assertTrue(slot instanceof DegradeSlot); } @Test public void testLoadFirstInstance() { ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadFirstInstance(); assertNotNull(slot); assertTrue(slot instanceof NodeSelectorSlot); SlotChainBuilder chainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstance(); assertNotNull(chainBuilder); assertTrue(chainBuilder instanceof SlotChainBuilder); InitFunc initFunc = SpiLoader.of(InitFunc.class).loadFirstInstance(); assertNotNull(initFunc); assertTrue(initFunc instanceof MetricCallbackInit); } @Test public void testLoadFirstInstanceOrDefault() { SlotChainBuilder slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); assertNotNull(slotChainBuilder); assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); } @Test public void testLoadDefaultInstance() { SlotChainBuilder slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadDefaultInstance(); assertNotNull(slotChainBuilder); assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); } @Test public void testLoadInstanceByClass() { ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadInstance(StatisticSlot.class); assertNotNull(slot); assertTrue(slot instanceof StatisticSlot); } @Test public void testLoadInstanceByAliasName() { ProcessorSlot slot = SpiLoader.of(ProcessorSlot.class).loadInstance( "com.alibaba.csp.sentinel.slots.statistic.StatisticSlot"); assertNotNull(slot); assertTrue(slot instanceof StatisticSlot); } @Test public void testToString() { SpiLoader spiLoader = SpiLoader.of(ProcessorSlot.class); assertEquals("com.alibaba.csp.sentinel.spi.SpiLoader[com.alibaba.csp.sentinel.slotchain.ProcessorSlot]" , spiLoader.toString()); } /** * Following test cases are for some test Interfaces. */ @Test public void test_TestNoSpiFileInterface() { SpiLoader loader = SpiLoader.of(TestNoSpiFileInterface.class); List providers = loader.loadInstanceList(); assertTrue(providers.size() == 0); List sortedProviders = loader.loadInstanceListSorted(); assertTrue(sortedProviders.size() == 0); TestNoSpiFileInterface firstProvider = loader.loadFirstInstance(); assertNull(firstProvider); TestNoSpiFileInterface defaultProvider = loader.loadDefaultInstance(); assertNull(defaultProvider); } @Test public void test_TestNoProviderInterface() { List providers = SpiLoader.of(TestNoProviderInterface.class).loadInstanceList(); assertTrue(providers.size() == 0); } @Test public void test_TestInterface() { SpiLoader loader = SpiLoader.of(TestInterface.class); List providers = loader.loadInstanceList(); assertTrue(providers.size() == 4); assertTrue(providers.get(0) instanceof TestOneProvider); assertTrue(providers.get(1) instanceof TestTwoProvider); assertTrue(providers.get(2) instanceof TestThreeProvider); assertTrue(providers.get(3) instanceof TestFiveProvider); List sortedProviders = loader.loadInstanceListSorted(); assertEquals(sortedProviders.size(), 4); assertTrue(sortedProviders.get(0) instanceof TestThreeProvider); assertTrue(sortedProviders.get(1) instanceof TestFiveProvider); assertTrue(sortedProviders.get(2) instanceof TestTwoProvider); assertTrue(sortedProviders.get(3) instanceof TestOneProvider); assertSame(providers.get(0), sortedProviders.get(3)); assertSame(providers.get(1), sortedProviders.get(2)); assertNotSame(providers.get(2), sortedProviders.get(0)); assertSame(providers.get(3), sortedProviders.get(1)); assertTrue(loader.loadDefaultInstance() instanceof TestFiveProvider); assertTrue(loader.loadHighestPriorityInstance() instanceof TestThreeProvider); assertTrue(loader.loadLowestPriorityInstance() instanceof TestOneProvider); assertTrue(loader.loadInstance("two") instanceof TestTwoProvider); assertSame(loader.loadInstance("two"), loader.loadInstance("two")); try { loader.loadInstance("one"); fail(); } catch (Exception e) { assertTrue(e instanceof SpiLoaderException); assertThat(e.getMessage(), containsString("no Provider class's aliasName is one")); } TestInterface oneProvider1 = loader.loadInstance(TestOneProvider.class); assertNotNull(oneProvider1); TestInterface oneProvider2 = loader.loadInstance(TestOneProvider.class); assertNotNull(oneProvider2); assertSame(oneProvider1, oneProvider2); try { loader.loadInstance(TestInterface.class); fail(); } catch (Exception e) { assertTrue(e instanceof SpiLoaderException); assertThat(e.getMessage(), containsString("is not subtype of")); } try { loader.loadInstance(TestFourProvider.class); fail(); } catch (Exception e) { assertTrue(e instanceof SpiLoaderException); assertThat(e.getMessage(), allOf(containsString("is not Provider class of") , containsString("check if it is in the SPI configuration file?"))); } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestFiveProvider.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ @Spi(value = "five", isDefault = true, order = -270) public class TestFiveProvider implements TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestFourProvider.java ================================================ package com.alibaba.csp.sentinel.spi; /** * This Provider class isn't configured in SPI file. * * @author cdfive */ @Spi(value = "four", isSingleton = true, order = -400) public class TestFourProvider implements TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestInterface.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ public interface TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestNoProviderInterface.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ public interface TestNoProviderInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestNoSpiFileInterface.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ public interface TestNoSpiFileInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestOneProvider.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ public class TestOneProvider implements TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestThreeProvider.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ @Spi(order = -300, isSingleton = false) public class TestThreeProvider implements TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/TestTwoProvider.java ================================================ package com.alibaba.csp.sentinel.spi; /** * @author cdfive */ @Spi(value = "two", isSingleton = true, order = -200) public class TestTwoProvider implements TestInterface { } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.test; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import com.alibaba.csp.sentinel.util.TimeUtil; import java.util.concurrent.ThreadLocalRandom; /** * Mock support for {@link TimeUtil}. * * @author jason */ @RunWith(MockitoJUnitRunner.class) public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long t) { currentMillis += t; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } protected final boolean entryAndSleepFor(MockedStatic mocked, String res, int sleepMs) { Entry entry = null; try { entry = SphU.entry(res); sleep(mocked, sleepMs); } catch (BlockException ex) { return false; } catch (Exception ex) { Tracer.traceEntry(ex, entry); } finally { if (entry != null) { entry.exit(); } } return true; } protected final boolean entryWithErrorIfPresent(MockedStatic mocked, String res, Exception ex) { Entry entry = null; try { entry = SphU.entry(res); if (ex != null) { Tracer.traceEntry(ex, entry); } sleep(mocked, ThreadLocalRandom.current().nextInt(5, 10)); } catch (BlockException b) { return false; } finally { if (entry != null) { entry.exit(); } } return true; } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/ConfigUtilTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import org.junit.Assert; import org.junit.Test; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Properties; import static com.alibaba.csp.sentinel.log.LogBase.LOG_DIR; import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; /** * @author lianglin * @since 1.7.0 */ public class ConfigUtilTest { @Test public void testLoadProperties() throws IOException { File file = null; String logOutputType = "utf-8", dir = "/data/logs/", fileName = "propertiesTest.properties"; try { String userDir = System.getProperty("user.dir"); file = new File(addSeparator(userDir) + "target/classes/" + fileName); if (!file.exists()) { file.createNewFile(); } BufferedWriter out = new BufferedWriter(new FileWriter(file)); out.write(LOG_OUTPUT_TYPE + "=" + logOutputType); out.write(System.getProperty("line.separator")); out.write(LOG_DIR + "=" + dir); out.flush(); out.close(); //Load from absolutePath Properties properties = ConfigUtil.loadProperties(file.getAbsolutePath()); Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE))); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); //Load from classPath properties = ConfigUtil.loadProperties(ConfigUtil.CLASSPATH_FILE_FLAG + fileName); Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE))); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); //Load from relativePath properties = ConfigUtil.loadProperties("target/classes/" + fileName); Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE))); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); } finally { if (file != null) { file.delete(); } } } //add Jvm parameter //-Dcsp.sentinel.charset="UTF-16" //@Test public void testLoadPropertiesWithCustomizedCharset() throws IOException { String charset = "UTF-16"; File file = null; String dir = "/data/logs/", fileName = "propertiesTest.properties"; try { String userDir = System.getProperty("user.dir"); file = new File(addSeparator(userDir) + "target/classes/" + fileName); if (!file.exists()) { file.createNewFile(); } OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), charset); out.write(LOG_DIR + "=" + dir); out.flush(); out.close(); //Load from absolutePath Properties properties = ConfigUtil.loadProperties(file.getAbsolutePath()); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); //Load from classPath properties = ConfigUtil.loadProperties(ConfigUtil.CLASSPATH_FILE_FLAG + fileName); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); //Load from relativePath properties = ConfigUtil.loadProperties("target/classes/" + fileName); Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR))); } finally { if (file != null) { file.delete(); } } } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/IdUtilTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import static org.junit.Assert.assertEquals; import org.junit.Test; public class IdUtilTest { @Test public void truncate() { assertEquals("(foo),(bar),(baz)", IdUtil.truncate(".(foo).,(bar).,(baz)")); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/MethodUtilTest.java ================================================ package com.alibaba.csp.sentinel.util; import java.lang.reflect.Method; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link MethodUtil}. * * @author Eric Zhao */ public class MethodUtilTest { @Before public void setUp() { MethodUtil.clearMethodMap(); } @After public void cleanUp() { MethodUtil.clearMethodMap(); } @Test public void testResolveMethodName() { Method fooMethod = null; for (Method m : GoodClass.class.getMethods()) { if (m.getName().contains("foo")) { fooMethod = m; break; } } assertNotNull(fooMethod); assertEquals("com.alibaba.csp.sentinel.util.MethodUtilTest$GoodClass:foo(long[],java.lang.String,java.lang.Integer[])", MethodUtil.resolveMethodName(fooMethod)); Method bazMethod = null; for (Method m : GoodClass.class.getMethods()) { if (m.getName().contains("baz")) { bazMethod = m; break; } } assertNotNull(bazMethod); assertEquals("com.alibaba.csp.sentinel.util.MethodUtilTest$GoodClass:baz(double)", MethodUtil.resolveMethodName(bazMethod)); } interface GoodClass { void foo(long[] p1, String p2, Integer[] p3); String baz(double a); } @Test(expected = IllegalArgumentException.class) public void testResolveNullMethod() { MethodUtil.resolveMethodName(null); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/StringUtilTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import org.junit.Assert; import org.junit.Test; public class StringUtilTest { @Test public void testCapitalize() { Assert.assertNull(StringUtil.capitalize(null)); Assert.assertEquals("Foo", StringUtil.capitalize("foo")); } @Test public void testEqualsIgnoreCase() { Assert.assertFalse(StringUtil.equalsIgnoreCase("", "BCCC")); Assert.assertFalse(StringUtil.equalsIgnoreCase(null, "")); Assert.assertTrue(StringUtil.equalsIgnoreCase("", "")); Assert.assertTrue(StringUtil.equalsIgnoreCase("BcCc", "BCCC")); Assert.assertTrue(StringUtil.equalsIgnoreCase(null, null)); } @Test public void testEquals() { Assert.assertFalse(StringUtil.equals(null, "")); Assert.assertFalse(StringUtil.equals("\"", "\"#\"\"\"\"\"\"")); Assert.assertTrue(StringUtil.equals(null, null)); } @Test public void testIsBlank() { Assert.assertFalse(StringUtil.isBlank("!!!!")); Assert.assertTrue(StringUtil.isBlank(null)); Assert.assertTrue(StringUtil.isBlank("\n\n")); Assert.assertTrue(StringUtil.isBlank("")); } @Test public void testIsEmpty() { Assert.assertFalse(StringUtil.isEmpty("bar")); Assert.assertTrue(StringUtil.isEmpty("")); } @Test public void testIsNotBlank() { Assert.assertFalse(StringUtil.isNotBlank("")); Assert.assertTrue(StringUtil.isNotBlank("\"###")); } @Test public void testIsNotEmpty() { Assert.assertFalse(StringUtil.isNotEmpty("")); Assert.assertTrue(StringUtil.isNotEmpty("foo")); } @Test public void testTrim() { Assert.assertNull(StringUtil.trim(null)); Assert.assertEquals("", StringUtil.trim("")); Assert.assertEquals("foo", StringUtil.trim("foo ")); } @Test public void testTrimToEmpty() { Assert.assertEquals("", StringUtil.trimToEmpty("")); Assert.assertEquals("", StringUtil.trimToEmpty(null)); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/TimeUtilTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.function.Tuple2; /** * @author jason * */ public class TimeUtilTest { private void waitFor(int step, int seconds) throws InterruptedException { for (int i = 0; i < seconds; i ++) { Tuple2 qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis()); RecordLog.info("step {} qps: reads={}, writes={}", step, qps.r1, qps.r2); Thread.sleep(1000); } } @Test public void test() throws InterruptedException { final AtomicInteger delayTime = new AtomicInteger(50); final AtomicBoolean shouldShutdown = new AtomicBoolean(); for (int i = 0; i < 10; i ++) { new Thread(new Runnable() { @Override public void run() { long last = 0; while (true) { if (shouldShutdown.get()) { break; } long now = TimeUtil.currentTimeMillis(); int delay = delayTime.get(); if (delay < 1) { if (last > now) { System.err.println("wrong value"); } last = now; continue; } try { Thread.sleep(delay); } catch (InterruptedException e) { } if (last > now) { System.err.println("incorrect value"); } last = now; } } }).start(); } Tuple2 qps; waitFor(1, 4); // initial state assertEquals(TimeUtil.STATE.IDLE, TimeUtil.instance().getState()); qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis()); assertTrue(qps.r1 < 1000); assertTrue(qps.r2 < 1000); // to RUNNING delayTime.set(0); // wait statistics to be stable waitFor(2, 8); qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis()); assertEquals(TimeUtil.STATE.RUNNING, TimeUtil.instance().getState()); assertTrue(qps.r1 > 1000); assertTrue(qps.r2 <= 1000); // back delayTime.set(100); // wait statistics to be stable waitFor(3, 8); qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis()); assertEquals(TimeUtil.STATE.IDLE, TimeUtil.instance().getState()); assertTrue(qps.r1 < 1000); assertTrue(qps.r2 < 1000); shouldShutdown.set(true); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/VersionUtilTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util; import static org.junit.Assert.*; import org.junit.Assert; import org.junit.Test; public class VersionUtilTest { @Test public void testGetDefaultVersion() { String defaultVersion = "1.0"; String version = VersionUtil.getVersion(defaultVersion); // Manifest cannot be load before package. Assert.assertEquals(defaultVersion, version); } @Test public void testFromVersionString() { assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3")); assertEquals(0x01020304, VersionUtil.fromVersionString("1.2.3.4")); assertEquals(0x0102ff04, VersionUtil.fromVersionString("1.2.255.4")); assertEquals(0xffffffff, VersionUtil.fromVersionString("255.255.255.255")); assertEquals(0, VersionUtil.fromVersionString("1.255.256.0")); assertEquals(0x01020000, VersionUtil.fromVersionString("1.2.")); assertEquals(0x01000000, VersionUtil.fromVersionString("1")); assertEquals(0x01020000, VersionUtil.fromVersionString("1.2")); assertEquals(0, VersionUtil.fromVersionString("test")); assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3-")); assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3b")); assertEquals(0x01023c00, VersionUtil.fromVersionString("1.2.60.sec9")); assertEquals(0x01023c00, VersionUtil.fromVersionString("1.2.60-internal")); } } ================================================ FILE: sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/function/Tuple2Test.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.util.function; import org.junit.Assert; import org.junit.Test; public class Tuple2Test { @Test public void testSwap() { Tuple2 tupleOld = new Tuple2(1, 2); Tuple2 tupleNew = tupleOld.swap(); Assert.assertEquals(new Tuple2(2, 1), tupleNew); } } ================================================ FILE: sentinel-core/src/test/resources/META-INF/services/com.alibaba.csp.sentinel.spi.TestInterface ================================================ # One com.alibaba.csp.sentinel.spi.TestOneProvider com.alibaba.csp.sentinel.spi.TestTwoProvider # Two com.alibaba.csp.sentinel.spi.TestThreeProvider com.alibaba.csp.sentinel.spi.TestFiveProvider ================================================ FILE: sentinel-core/src/test/resources/META-INF/services/com.alibaba.csp.sentinel.spi.TestNoProviderInterface ================================================ ================================================ FILE: sentinel-dashboard/Dockerfile ================================================ FROM amd64/buildpack-deps:buster-curl as installer ARG SENTINEL_VERSION=1.8.9 RUN set -x \ && curl -SL --output /home/sentinel-dashboard.jar https://github.com/alibaba/Sentinel/releases/download/${SENTINEL_VERSION}/sentinel-dashboard-${SENTINEL_VERSION}.jar FROM openjdk:8-jre-slim # copy sentinel jar COPY --from=installer ["/home/sentinel-dashboard.jar", "/home/sentinel-dashboard.jar"] ENV JAVA_OPTS '-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080' RUN chmod -R +x /home/sentinel-dashboard.jar EXPOSE 8080 CMD java ${JAVA_OPTS} -jar /home/sentinel-dashboard.jar ================================================ FILE: sentinel-dashboard/README.md ================================================ # Sentinel 控制台 ## 0. 概述 Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。 ## 1. 编译和启动 ### 1.1 如何编译 使用如下命令将代码打包成一个 fat jar: ```bash mvn clean package ``` ### 1.2 如何启动 使用如下命令启动编译后的控制台: ```bash java -Dserver.port=8080 \ -Dcsp.sentinel.dashboard.server=localhost:8080 \ -Dproject.name=sentinel-dashboard \ -jar target/sentinel-dashboard.jar ``` 上述命令中我们指定几个 JVM 参数,其中 `-Dserver.port=8080` 是 Spring Boot 的参数, 用于指定 Spring Boot 服务端启动端口为 `8080`。其余几个是 Sentinel 客户端的参数。 为便于演示,我们对控制台本身加入了流量控制功能,具体做法是引入 Sentinel 提供的 `CommonFilter` 这个 Servlet Filter。 上述 JVM 参数的含义是: | 参数 | 作用 | | -------- | -------- | | `-Dcsp.sentinel.dashboard.server=localhost:8080` | 向 Sentinel 接入端指定控制台的地址 | | `-Dproject.name=sentinel-dashboard` | 向 Sentinel 指定应用名称,比如上面对应的应用名称就为 `sentinel-dashboard` | 全部的配置项可以参考 [启动配置项文档](https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9)。 经过上述配置,控制台启动后会自动向自己发送心跳。程序启动后浏览器访问 `localhost:8080` 即可访问 Sentinel 控制台。 从 Sentinel 1.6.0 开始,Sentinel 控制台支持简单的**登录**功能,默认用户名和密码都是 `sentinel`。用户可以通过如下参数进行配置: - `-Dsentinel.dashboard.auth.username=sentinel` 用于指定控制台的登录用户名为 `sentinel`; - `-Dsentinel.dashboard.auth.password=123456` 用于指定控制台的登录密码为 `123456`;如果省略这两个参数,默认用户和密码均为 `sentinel`; - `-Dserver.servlet.session.timeout=7200` 用于指定 Spring Boot 服务端 session 的过期时间,如 `7200` 表示 7200 秒;`60m` 表示 60 分钟,默认为 30 分钟; ## 2. 客户端接入 选择合适的方式接入 Sentinel,然后在应用启动时加入 JVM 参数 `-Dcsp.sentinel.dashboard.server=consoleIp:port` 指定控制台地址和端口。 确保客户端有访问量,**Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包**,将客户端纳入到控制台的管辖之下。 客户端接入的详细步骤请参考 [Wiki 文档](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0#3-%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8E%A5%E5%85%A5%E6%8E%A7%E5%88%B6%E5%8F%B0)。 ## 3. 验证是否接入成功 客户端正确配置并启动后,会**在初次调用后**主动向控制台发送心跳包,汇报自己的存在; 控制台收到客户端心跳包之后,会在左侧导航栏中显示该客户端信息。如果控制台能够看到客户端的机器信息,则表明客户端接入成功了。 ## 6. 构建Docker镜像 ```bash docker build --build-arg SENTINEL_VERSION=1.8.9 -t ${REGISTRY}/sentinel-dashboard:v1.8.9 . ``` *注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,不提供安全可靠保障。若希望在生产环境使用请根据[文档](https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel)自行进行定制和改造。* 更多:[控制台功能介绍](./Sentinel_Dashboard_Feature.md)。 ================================================ FILE: sentinel-dashboard/Sentinel_Dashboard_Feature.md ================================================ # Sentinel 控制台功能介绍 ## 0. 概述 Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。使用 Sentinel 控制台的流程如下: ```plaintext 客户端接入 -> 机器自发现 -> 查看簇点链路 -> 配置流控规则 -> 查看流控效果 ``` ## 1. 功能介绍 ### 1.1 机器自发现 Sentinel 提供内置的机器自发现功能,无需依赖第三方服务发现组件即可实现客户端的发现。点击 Sentinel 控制台左侧导航栏的“机器列表”菜单,可查看集群机器数量和机器健康状况。 ### 1.2 簇点链路自发现 Sentinel 将每一个需要流控的 URL 或者接口称为一个资源,并使用URL地址或方法签名表示资源。Sentinel 会自动发现所有潜在的资源和资源之间的调用链,并在“簇点链路”页面展示。 资源是设置流控规则的载体,通过簇点链路自发现,可以方便的对重要资源设置流控规则、熔断降级规则等。 > 注意:**客户端有访问量之后,才能在簇点链路页面看到资源监控(lazy-initializing)**。 ### 1.3 实时监控 Sentinel 监控功能能够实时查看集群中每个资源的实时访问以及流控情况。控制台左侧导航栏的“实时监控”菜单对应该功能。 ### 1.4 流控降级规则设置 Sentinel 提供了多种规则来保护系统的不同部分。流量控制规则用于保护服务提供方,熔断降级规则用于保护服务消费方,系统保护规则用于保护整个系统。 #### 1.4.1 流量控制规则 流量控制规则用于保护服务提供方,服务提供方指任何可以对外提供服务的系统,比如向终端用户提供 Web 服务的 Web 应用,向微服务消费方提供服务的 Dubbo Service Provider 等。 系统的服务能力是有限的,如果消费方请求速度过高,则采用相应的保护策略,或是直接拒绝,或是排队等待。通过“流控规则”页面可以查看和配置流量控制规则。 #### 1.4.2 熔断降级规则 降级规则用于保护服务消费方。在微服务架构中,业务系统通常要依赖多个服务,依赖的某个服务不可用将会影响业务系统的可用性。解决这个问题的方法是及时发现服务的“不可用”状态,在调用时快速失败而不是等待调动超时或者重试。Sentinel 通过熔断降级来达到快速失败的目的。通过“降级规则”页面可以查看和配置降级规则。 #### 1.4.3 系统保护规则 系统保护规则(简称`系统规则`)用于保护整个系统指标处于安全水位。无论是服务提供方还是消费方,活跃线程数过多、系统LOAD等过高都是系统处于高水位的指标。当系统水位过高时,系统应拒绝对外提供服务以便迅速降低资源占用。通过“系统规则”页面可以查看和设置系统保护规则。 ## 2. 限制 本控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如**持久化的后端数据库、可靠的配置中心**等。 目前 Sentinel 采用内存态的方式存储监控和规则数据,监控最长存储时间为 5 分钟,控制台重启后数据丢失。 ## 3. 配置项 控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:`System.getProperty()` 和 `System.getenv()`,同时存在时后者可以覆盖前者。 > 通过环境变量进行配置时,因为不支持 `.` 所以需要将其更换为 `_`。 项|类型|默认值|最小值|描述 ---|---|---|---|--- sentinel.dashboard.auth.username|String|sentinel|无|登录控制台的用户名,默认为 `sentinel` sentinel.dashboard.auth.password|String|sentinel|无|登录控制台的密码,默认为 `sentinel` sentinel.dashboard.app.hideAppNoMachineMillis|Integer|0|60000|是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 sentinel.dashboard.removeAppNoMachineMillis|Integer|0|120000|是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 sentinel.dashboard.unhealthyMachineMillis|Integer|60000|30000|主机失联判定,不可关闭 sentinel.dashboard.autoRemoveMachineMillis|Integer|0|300000|距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 配置示例: - 命令行方式: ```shell java -Dsentinel.dashboard.app.hideAppNoMachineMillis=60000 ``` - Java 方式: ```java System.setProperty("sentinel.dashboard.app.hideAppNoMachineMillis", "60000"); ``` - 环境变量方式: ```shell sentinel_dashboard_app_hideAppNoMachineMillis=60000 ``` 更多: - [Sentinel 控制台启动和客户端接入](./README.md) - [控制台 Wiki]() ================================================ FILE: sentinel-dashboard/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-dashboard jar @ 2.5.12 4.0.1 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-web-servlet ${project.version} com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-parameter-flow-control ${project.version} com.alibaba.csp sentinel-api-gateway-adapter-common ${project.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-logging ${spring.boot.version} org.springframework.boot spring-boot-starter-test ${spring.boot.version} test log4j log4j 1.2.17 commons-lang commons-lang 2.6 org.apache.httpcomponents httpclient 4.5.13 org.apache.httpcomponents httpcore 4.4.5 org.apache.httpcomponents httpasyncclient 4.1.3 org.apache.httpcomponents httpcore-nio 4.4.6 com.alibaba fastjson com.alibaba.csp sentinel-datasource-nacos test com.ctrip.framework.apollo apollo-openapi 1.2.0 test org.apache.curator curator-recipes ${curator.version} test junit junit test org.mockito mockito-core test com.github.stefanbirkner system-rules 1.16.1 test sentinel-dashboard org.apache.maven.plugins maven-resources-plugin ${resource.delimiter} false org.springframework.boot spring-boot-maven-plugin ${spring.boot.version} true com.alibaba.csp.sentinel.dashboard.DashboardApplication repackage org.apache.maven.plugins maven-deploy-plugin ${maven.deploy.version} true org.apache.maven.plugins maven-gpg-plugin true src/main/resources true src/main/webapp/ resources/node_modules/** ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/DashboardApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard; import com.alibaba.csp.sentinel.init.InitExecutor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Sentinel dashboard application. * * @author Carpenter Lee */ @SpringBootApplication public class DashboardApplication { public static void main(String[] args) { triggerSentinelInit(); SpringApplication.run(DashboardApplication.class, args); } private static void triggerSentinelInit() { new Thread(() -> InitExecutor.doInit()).start(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthAction.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author lkxiaolou * @since 1.7.1 */ @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.METHOD}) public @interface AuthAction { /** * @return the privilege type */ AuthService.PrivilegeType value(); /** * @return the target name to control */ String targetName() default "app"; /** * @return the message when permission is denied */ String message() default "Permission denied"; } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; /** * Interface for authentication and authorization. * * @author Carpenter Lee * @since 1.5.0 */ public interface AuthService { /** * Get the authentication user. * * @param request the request contains the user information * @return the auth user represent the current user, when the user is illegal, a null value will return. */ AuthUser getAuthUser(R request); /** * Privilege type. */ enum PrivilegeType { /** * Read rule */ READ_RULE, /** * Create or modify rule */ WRITE_RULE, /** * Delete rule */ DELETE_RULE, /** * Read metrics */ READ_METRIC, /** * Add machine */ ADD_MACHINE, /** * All privileges above are granted. */ ALL } /** * Represents the current user. */ interface AuthUser { /** * Query whether current user has the specific privilege to the target, the target * may be an app name or an ip address, or other destination. *

          * This method will use return value to represent whether user has the specific * privileges to the target, but to throw a RuntimeException to represent no auth * is also a good way. *

          * * @param target the target to check * @param privilegeType the privilege type to check * @return if current user has the specific privileges to the target, return true, * otherwise return false. */ boolean authTarget(String target, PrivilegeType privilegeType); /** * Check whether current user is a super-user. * * @return if current user is super user return true, else return false. */ boolean isSuperUser(); /** * Get current user's nick name. * * @return current user's nick name. */ String getNickName(); /** * Get current user's login name. * * @return current user's login name. */ String getLoginName(); /** * Get current user's ID. * * @return ID of current user */ String getId(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthorizationInterceptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import org.springframework.web.servlet.HandlerInterceptor; /** * The web interceptor for privilege-based authorization. * * @author lkxiaolou * @author wxq * @since 1.7.1 */ public interface AuthorizationInterceptor extends HandlerInterceptor { } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/DefaultAuthorizationInterceptor.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.fastjson.JSON; import org.springframework.web.method.HandlerMethod; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; /** * The web interceptor for privilege-based authorization. *

          * move from old {@link AuthorizationInterceptor}. * * @author lkxiaolou * @author wxq * @since 1.7.1 */ public class DefaultAuthorizationInterceptor implements AuthorizationInterceptor { private final AuthService authService; public DefaultAuthorizationInterceptor(AuthService authService) { this.authService = authService; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler.getClass().isAssignableFrom(HandlerMethod.class)) { Method method = ((HandlerMethod) handler).getMethod(); AuthAction authAction = method.getAnnotation(AuthAction.class); if (authAction != null) { AuthService.AuthUser authUser = authService.getAuthUser(request); if (authUser == null) { responseNoPrivilegeMsg(response, authAction.message()); return false; } String target = request.getParameter(authAction.targetName()); if (!authUser.authTarget(target, authAction.value())) { responseNoPrivilegeMsg(response, authAction.message()); return false; } } } return true; } private void responseNoPrivilegeMsg(HttpServletResponse response, String message) throws IOException { Result result = Result.ofFail(-1, message); response.addHeader("Content-Type", "application/json;charset=UTF-8"); response.getOutputStream().write(JSON.toJSONBytes(result)); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/DefaultLoginAuthenticationFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.util.AntPathMatcher; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; /** *

          The Servlet filter for authentication.

          * *

          Note: some urls are excluded as they needn't auth, such as:

          *
            *
          • index url: {@code /}
          • *
          • authentication request url: {@code /login}, {@code /logout}
          • *
          • machine registry: {@code /registry/machine}
          • *
          • static resources
          • *
          *

          * The excluded urls and urlSuffixes could be configured in {@code application.properties} file. * * @author cdfive * @since 1.6.0 */ public class DefaultLoginAuthenticationFilter implements LoginAuthenticationFilter { private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); private static final String URL_SUFFIX_DOT = "."; /** * Some urls which needn't auth, such as /auth/login, /registry/machine and so on. */ @Value("#{'${auth.filter.exclude-urls}'.split(',')}") private List authFilterExcludeUrls; /** * Some urls with suffixes which needn't auth, such as htm, html, js and so on. */ @Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}") private List authFilterExcludeUrlSuffixes; /** * Authentication using AuthService interface. */ private final AuthService authService; public DefaultLoginAuthenticationFilter(AuthService authService) { this.authService = authService; } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String servletPath = httpRequest.getServletPath(); // Exclude the urls which needn't auth boolean authFilterExcludeMatch = authFilterExcludeUrls.stream() .anyMatch(authFilterExcludeUrl -> PATH_MATCHER.match(authFilterExcludeUrl, servletPath)); if (authFilterExcludeMatch) { chain.doFilter(request, response); return; } // Exclude the urls with suffixes which needn't auth for (String authFilterExcludeUrlSuffix : authFilterExcludeUrlSuffixes) { if (StringUtils.isBlank(authFilterExcludeUrlSuffix)) { continue; } // Add . for url suffix so that we needn't add . in property file if (!authFilterExcludeUrlSuffix.startsWith(URL_SUFFIX_DOT)) { authFilterExcludeUrlSuffix = URL_SUFFIX_DOT + authFilterExcludeUrlSuffix; } if (servletPath.endsWith(authFilterExcludeUrlSuffix)) { chain.doFilter(request, response); return; } } AuthService.AuthUser authUser = authService.getAuthUser(httpRequest); HttpServletResponse httpResponse = (HttpServletResponse) response; if (authUser == null) { // If auth fail, set response status code to 401 httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); } else { chain.doFilter(request, response); } } @Override public void destroy() { } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/FakeAuthServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; /** * A fake AuthService implementation, which will pass all user auth checking. * * @author Carpenter Lee * @since 1.5.0 */ public class FakeAuthServiceImpl implements AuthService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public FakeAuthServiceImpl() { this.logger.warn("there is no auth, use {} by implementation {}", AuthService.class, this.getClass()); } @Override public AuthUser getAuthUser(HttpServletRequest request) { return new AuthUserImpl(); } static final class AuthUserImpl implements AuthUser { @Override public boolean authTarget(String target, PrivilegeType privilegeType) { // fake implementation, always return true return true; } @Override public boolean isSuperUser() { // fake implementation, always return true return true; } @Override public String getNickName() { return "FAKE_NICK_NAME"; } @Override public String getLoginName() { return "FAKE_LOGIN_NAME"; } @Override public String getId() { return "FAKE_EMP_ID"; } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/LoginAuthenticationFilter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import javax.servlet.Filter; /** *

          The Servlet filter for authentication.

          * *

          Note: some urls are excluded as they needn't auth, such as:

          *
            *
          • index url: {@code /}
          • *
          • authentication request url: {@code /login}, {@code /logout}
          • *
          • machine registry: {@code /registry/machine}
          • *
          • static resources
          • *
          *

          * The excluded urls and urlSuffixes could be configured in {@code application.properties} file. * * @author cdfive * @author wxq * @since 1.6.0 */ public interface LoginAuthenticationFilter extends Filter { } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/SimpleWebAuthServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.auth; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * @author cdfive * @since 1.6.0 */ public class SimpleWebAuthServiceImpl implements AuthService { public static final String WEB_SESSION_KEY = "session_sentinel_admin"; @Override public AuthUser getAuthUser(HttpServletRequest request) { HttpSession session = request.getSession(); Object sentinelUserObj = session.getAttribute(SimpleWebAuthServiceImpl.WEB_SESSION_KEY); if (sentinelUserObj != null && sentinelUserObj instanceof AuthUser) { return (AuthUser) sentinelUserObj; } return null; } public static final class SimpleWebAuthUserImpl implements AuthUser { private String username; public SimpleWebAuthUserImpl(String username) { this.username = username; } @Override public boolean authTarget(String target, PrivilegeType privilegeType) { return true; } @Override public boolean isSuperUser() { return true; } @Override public String getNickName() { return username; } @Override public String getLoginName() { return username; } @Override public String getId() { return username; } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/CommandFailedException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.client; /** * @author Eric Zhao */ public class CommandFailedException extends RuntimeException { public CommandFailedException() {} public CommandFailedException(String message) { super(message); } @Override public synchronized Throwable fillInStackTrace() { return this; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/CommandNotFoundException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.client; /** * @author Eric Zhao * @since 0.2.1 */ public class CommandNotFoundException extends Exception { public CommandNotFoundException() { } public CommandNotFoundException(String message) { super(message); } @Override public synchronized Throwable fillInStackTrace() { return this; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.client; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.command.CommandConstants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterClientInfoVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterStateSimpleEntity; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; import com.alibaba.csp.sentinel.dashboard.util.VersionUtils; import org.apache.http.Consts; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.concurrent.FutureCallback; import org.apache.http.conn.util.InetAddressUtils; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.impl.nio.reactor.IOReactorConfig; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; /** * Communicate with Sentinel client. * * @author leyou */ @Component public class SentinelApiClient { private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class); private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset()); private static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; private static final String HTTP_HEADER_CONTENT_TYPE_URLENCODED = ContentType.create(URLEncodedUtils.CONTENT_TYPE).toString(); private static final String RESOURCE_URL_PATH = "jsonTree"; private static final String CLUSTER_NODE_PATH = "clusterNode"; private static final String GET_RULES_PATH = "getRules"; private static final String SET_RULES_PATH = "setRules"; private static final String GET_PARAM_RULE_PATH = "getParamFlowRules"; private static final String SET_PARAM_RULE_PATH = "setParamFlowRules"; private static final String FETCH_CLUSTER_MODE_PATH = "getClusterMode"; private static final String MODIFY_CLUSTER_MODE_PATH = "setClusterMode"; private static final String FETCH_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/fetchConfig"; private static final String MODIFY_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/modifyConfig"; private static final String FETCH_CLUSTER_SERVER_BASIC_INFO_PATH = "cluster/server/info"; private static final String MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH = "cluster/server/modifyTransportConfig"; private static final String MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH = "cluster/server/modifyFlowConfig"; private static final String MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH = "cluster/server/modifyNamespaceSet"; private static final String FETCH_GATEWAY_API_PATH = "gateway/getApiDefinitions"; private static final String MODIFY_GATEWAY_API_PATH = "gateway/updateApiDefinitions"; private static final String FETCH_GATEWAY_FLOW_RULE_PATH = "gateway/getRules"; private static final String MODIFY_GATEWAY_FLOW_RULE_PATH = "gateway/updateRules"; private static final String FLOW_RULE_TYPE = "flow"; private static final String DEGRADE_RULE_TYPE = "degrade"; private static final String SYSTEM_RULE_TYPE = "system"; private static final String AUTHORITY_TYPE = "authority"; private CloseableHttpAsyncClient httpClient; private static final SentinelVersion version160 = new SentinelVersion(1, 6, 0); private static final SentinelVersion version171 = new SentinelVersion(1, 7, 1); @Autowired private AppManagement appManagement; public SentinelApiClient() { IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(10000) .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2).build(); httpClient = HttpAsyncClients.custom().setRedirectStrategy(new DefaultRedirectStrategy() { @Override protected boolean isRedirectable(final String method) { return false; } }).setMaxConnTotal(4000).setMaxConnPerRoute(1000).setDefaultIOReactorConfig(ioConfig).build(); httpClient.start(); } private boolean isSuccess(int statusCode) { return statusCode >= 200 && statusCode < 300; } private boolean isCommandNotFound(int statusCode, String body) { return statusCode == 400 && StringUtil.isNotEmpty(body) && body.contains(CommandConstants.MSG_UNKNOWN_COMMAND_PREFIX); } protected boolean isSupportPost(String app, String ip, int port) { return StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) .flatMap(e -> e.getMachine(ip, port)) .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) .map(v -> v.greaterOrEqual(version160))) .orElse(false); } /** * Check whether target instance (identified by tuple of app-ip:port) * supports the form of "xxxxx; xx=xx" in "Content-Type" header. * * @param app target app name * @param ip target node's address * @param port target node's port */ protected boolean isSupportEnhancedContentType(String app, String ip, int port) { return StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) .flatMap(e -> e.getMachine(ip, port)) .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) .map(v -> v.greaterOrEqual(version171))) .orElse(false); } private StringBuilder queryString(Map params) { StringBuilder queryStringBuilder = new StringBuilder(); for (Entry entry : params.entrySet()) { if (StringUtil.isEmpty(entry.getValue())) { continue; } String name = urlEncode(entry.getKey()); String value = urlEncode(entry.getValue()); if (name != null && value != null) { if (queryStringBuilder.length() > 0) { queryStringBuilder.append('&'); } queryStringBuilder.append(name).append('=').append(value); } } return queryStringBuilder; } /** * Build an `HttpUriRequest` in POST way. * * @param url * @param params * @param supportEnhancedContentType see {@link #isSupportEnhancedContentType(String, String, int)} * @return */ protected static HttpUriRequest postRequest(String url, Map params, boolean supportEnhancedContentType) { HttpPost httpPost = new HttpPost(url); if (params != null && params.size() > 0) { List list = new ArrayList<>(params.size()); for (Entry entry : params.entrySet()) { list.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } httpPost.setEntity(new UrlEncodedFormEntity(list, Consts.UTF_8)); if (!supportEnhancedContentType) { httpPost.setHeader(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_CONTENT_TYPE_URLENCODED); } } return httpPost; } private String urlEncode(String str) { try { return URLEncoder.encode(str, DEFAULT_CHARSET.name()); } catch (UnsupportedEncodingException e) { logger.info("encode string error: {}", str, e); return null; } } private String getBody(HttpResponse response) throws Exception { Charset charset = null; try { String contentTypeStr = response.getFirstHeader(HTTP_HEADER_CONTENT_TYPE).getValue(); if (StringUtil.isNotEmpty(contentTypeStr)) { ContentType contentType = ContentType.parse(contentTypeStr); charset = contentType.getCharset(); } } catch (Exception ignore) { } return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); } /** * With no param * * @param ip * @param port * @param api * @return */ private CompletableFuture executeCommand(String ip, int port, String api, boolean useHttpPost) { return executeCommand(ip, port, api, null, useHttpPost); } /** * No app specified, force to GET * * @param ip * @param port * @param api * @param params * @return */ private CompletableFuture executeCommand(String ip, int port, String api, Map params, boolean useHttpPost) { return executeCommand(null, ip, port, api, params, useHttpPost); } /** * Prefer to execute request using POST * * @param app * @param ip * @param port * @param api * @param params * @return */ private CompletableFuture executeCommand(String app, String ip, int port, String api, Map params, boolean useHttpPost) { CompletableFuture future = new CompletableFuture<>(); if (StringUtil.isBlank(ip) || StringUtil.isBlank(api)) { future.completeExceptionally(new IllegalArgumentException("Bad URL or command name")); return future; } if (!InetAddressUtils.isIPv4Address(ip) && !InetAddressUtils.isIPv6Address(ip)) { future.completeExceptionally(new IllegalArgumentException("Bad IP")); return future; } if (!StringUtil.isEmpty(app) && !appManagement.isValidMachineOfApp(app, ip)) { future.completeExceptionally(new IllegalArgumentException("Given ip does not belong to given app")); return future; } StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append("http://"); urlBuilder.append(ip).append(':').append(port).append('/').append(api); if (params == null) { params = Collections.emptyMap(); } if (!useHttpPost || !isSupportPost(app, ip, port)) { // Using GET in older versions, append parameters after url if (!params.isEmpty()) { if (urlBuilder.indexOf("?") == -1) { urlBuilder.append('?'); } else { urlBuilder.append('&'); } urlBuilder.append(queryString(params)); } return executeCommand(new HttpGet(urlBuilder.toString())); } else { // Using POST return executeCommand( postRequest(urlBuilder.toString(), params, isSupportEnhancedContentType(app, ip, port))); } } private CompletableFuture executeCommand(HttpUriRequest request) { CompletableFuture future = new CompletableFuture<>(); httpClient.execute(request, new FutureCallback() { @Override public void completed(final HttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); try { String value = getBody(response); if (isSuccess(statusCode)) { future.complete(value); } else { if (isCommandNotFound(statusCode, value)) { future.completeExceptionally(new CommandNotFoundException(request.getURI().getPath())); } else { future.completeExceptionally(new CommandFailedException(value)); } } } catch (Exception ex) { future.completeExceptionally(ex); logger.error("HTTP request failed: {}", request.getURI().toString(), ex); } } @Override public void failed(final Exception ex) { future.completeExceptionally(ex); logger.error("HTTP request failed: {}", request.getURI().toString(), ex); } @Override public void cancelled() { future.complete(null); } }); return future; } public void close() throws Exception { httpClient.close(); } @Nullable private CompletableFuture> fetchItemsAsync(String ip, int port, String api, String type, Class ruleType) { AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); Map params = null; if (StringUtil.isNotEmpty(type)) { params = new HashMap<>(1); params.put("type", type); } return executeCommand(ip, port, api, params, false) .thenApply(json -> JSON.parseArray(json, ruleType)); } @Nullable private List fetchItems(String ip, int port, String api, String type, Class ruleType) { try { AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); Map params = null; if (StringUtil.isNotEmpty(type)) { params = new HashMap<>(1); params.put("type", type); } return fetchItemsAsync(ip, port, api, type, ruleType).get(); } catch (InterruptedException | ExecutionException e) { logger.error("Error when fetching items from api: {} -> {}", api, type, e); return null; } catch (Exception e) { logger.error("Error when fetching items: {} -> {}", api, type, e); return null; } } private List fetchRules(String ip, int port, String type, Class ruleType) { return fetchItems(ip, port, GET_RULES_PATH, type, ruleType); } private boolean setRules(String app, String ip, int port, String type, List entities) { if (entities == null) { return true; } try { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); String data = JSON.toJSONString( entities.stream().map(r -> r.toRule()).collect(Collectors.toList())); Map params = new HashMap<>(2); params.put("type", type); params.put("data", data); String result = executeCommand(app, ip, port, SET_RULES_PATH, params, true).get(); logger.info("setRules result: {}, type={}", result, type); return true; } catch (InterruptedException e) { logger.warn("setRules API failed: {}", type, e); return false; } catch (ExecutionException e) { logger.warn("setRules API failed: {}", type, e.getCause()); return false; } catch (Exception e) { logger.error("setRules API failed, type={}", type, e); return false; } } private CompletableFuture setRulesAsync(String app, String ip, int port, String type, List entities) { try { AssertUtil.notNull(entities, "rules cannot be null"); AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); String data = JSON.toJSONString( entities.stream().map(r -> r.toRule()).collect(Collectors.toList())); Map params = new HashMap<>(2); params.put("type", type); params.put("data", data); return executeCommand(app, ip, port, SET_RULES_PATH, params, true) .thenCompose(r -> { if ("success".equalsIgnoreCase(r.trim())) { return CompletableFuture.completedFuture(null); } return AsyncUtils.newFailedFuture(new CommandFailedException(r)); }); } catch (Exception e) { logger.error("setRulesAsync API failed, type={}", type, e); return AsyncUtils.newFailedFuture(e); } } public List fetchResourceOfMachine(String ip, int port, String type) { return fetchItems(ip, port, RESOURCE_URL_PATH, type, NodeVo.class); } /** * Fetch cluster node. * * @param ip ip to fetch * @param port port of the ip * @param includeZero whether zero value should in the result list. * @return */ public List fetchClusterNodeOfMachine(String ip, int port, boolean includeZero) { String type = "notZero"; if (includeZero) { type = "zero"; } return fetchItems(ip, port, CLUSTER_NODE_PATH, type, NodeVo.class); } public List fetchFlowRuleOfMachine(String app, String ip, int port) { List rules = fetchRules(ip, port, FLOW_RULE_TYPE, FlowRule.class); if (rules != null) { return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule)) .collect(Collectors.toList()); } else { return null; } } public List fetchDegradeRuleOfMachine(String app, String ip, int port) { List rules = fetchRules(ip, port, DEGRADE_RULE_TYPE, DegradeRule.class); if (rules != null) { return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule)) .collect(Collectors.toList()); } else { return null; } } public List fetchSystemRuleOfMachine(String app, String ip, int port) { List rules = fetchRules(ip, port, SYSTEM_RULE_TYPE, SystemRule.class); if (rules != null) { return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule)) .collect(Collectors.toList()); } else { return null; } } /** * Fetch all parameter flow rules from provided machine. * * @param app application name * @param ip machine client IP * @param port machine client port * @return all retrieved parameter flow rules * @since 0.2.1 */ public CompletableFuture> fetchParamFlowRulesOfMachine(String app, String ip, int port) { try { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); return fetchItemsAsync(ip, port, GET_PARAM_RULE_PATH, null, ParamFlowRule.class) .thenApply(rules -> rules.stream() .map(e -> ParamFlowRuleEntity.fromParamFlowRule(app, ip, port, e)) .collect(Collectors.toList()) ); } catch (Exception e) { logger.error("Error when fetching parameter flow rules", e); return AsyncUtils.newFailedFuture(e); } } /** * Fetch all authority rules from provided machine. * * @param app application name * @param ip machine client IP * @param port machine client port * @return all retrieved authority rules * @since 0.2.1 */ public List fetchAuthorityRulesOfMachine(String app, String ip, int port) { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); Map params = new HashMap<>(1); params.put("type", AUTHORITY_TYPE); List rules = fetchRules(ip, port, AUTHORITY_TYPE, AuthorityRule.class); return Optional.ofNullable(rules).map(r -> r.stream() .map(e -> AuthorityRuleEntity.fromAuthorityRule(app, ip, port, e)) .collect(Collectors.toList()) ).orElse(null); } /** * set rules of the machine. rules == null will return immediately; * rules.isEmpty() means setting the rules to empty. * * @param app * @param ip * @param port * @param rules * @return whether successfully set the rules. */ public boolean setFlowRuleOfMachine(String app, String ip, int port, List rules) { return setRules(app, ip, port, FLOW_RULE_TYPE, rules); } public CompletableFuture setFlowRuleOfMachineAsync(String app, String ip, int port, List rules) { return setRulesAsync(app, ip, port, FLOW_RULE_TYPE, rules); } /** * set rules of the machine. rules == null will return immediately; * rules.isEmpty() means setting the rules to empty. * * @param app * @param ip * @param port * @param rules * @return whether successfully set the rules. */ public boolean setDegradeRuleOfMachine(String app, String ip, int port, List rules) { return setRules(app, ip, port, DEGRADE_RULE_TYPE, rules); } /** * set rules of the machine. rules == null will return immediately; * rules.isEmpty() means setting the rules to empty. * * @param app * @param ip * @param port * @param rules * @return whether successfully set the rules. */ public boolean setSystemRuleOfMachine(String app, String ip, int port, List rules) { return setRules(app, ip, port, SYSTEM_RULE_TYPE, rules); } public boolean setAuthorityRuleOfMachine(String app, String ip, int port, List rules) { return setRules(app, ip, port, AUTHORITY_TYPE, rules); } public CompletableFuture setParamFlowRuleOfMachine(String app, String ip, int port, List rules) { if (rules == null) { return CompletableFuture.completedFuture(null); } if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { String data = JSON.toJSONString( rules.stream().map(ParamFlowRuleEntity::getRule).collect(Collectors.toList()) ); Map params = new HashMap<>(1); params.put("data", data); return executeCommand(app, ip, port, SET_PARAM_RULE_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Push parameter flow rules to client failed: " + e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when setting parameter flow rule", ex); return AsyncUtils.newFailedFuture(ex); } } // Cluster related public CompletableFuture fetchClusterMode(String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { return executeCommand(ip, port, FETCH_CLUSTER_MODE_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterStateSimpleEntity.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster mode", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture modifyClusterMode(String ip, int port, int mode) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { Map params = new HashMap<>(1); params.put("mode", String.valueOf(mode)); return executeCommand(ip, port, MODIFY_CLUSTER_MODE_PATH, params, false) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster mode: " + e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when modifying cluster mode", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture fetchClusterClientInfoAndConfig(String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { return executeCommand(ip, port, FETCH_CLUSTER_CLIENT_CONFIG_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterClientInfoVO.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster client config", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture modifyClusterClientConfig(String app, String ip, int port, ClusterClientConfig config) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { Map params = new HashMap<>(1); params.put("data", JSON.toJSONString(config)); return executeCommand(app, ip, port, MODIFY_CLUSTER_CLIENT_CONFIG_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster client config: " + e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when modifying cluster client config", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture modifyClusterServerFlowConfig(String app, String ip, int port, ServerFlowConfig config) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { Map params = new HashMap<>(1); params.put("data", JSON.toJSONString(config)); return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server flow config: " + e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when modifying cluster server flow config", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture modifyClusterServerTransportConfig(String app, String ip, int port, ServerTransportConfig config) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { Map params = new HashMap<>(2); params.put("port", config.getPort().toString()); params.put("idleSeconds", config.getIdleSeconds().toString()); return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, params, false) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server transport config: " + e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when modifying cluster server transport config", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture modifyClusterServerNamespaceSet(String app, String ip, int port, Set set) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { Map params = new HashMap<>(1); params.put("data", JSON.toJSONString(set)); return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server NamespaceSet", e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); } catch (Exception ex) { logger.warn("Error when modifying cluster server NamespaceSet", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture fetchClusterServerBasicInfo(String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { return executeCommand(ip, port, FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterServerStateVO.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster sever all config and basic info", ex); return AsyncUtils.newFailedFuture(ex); } } public CompletableFuture> fetchApis(String app, String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { return executeCommand(ip, port, FETCH_GATEWAY_API_PATH, false) .thenApply(r -> { List entities = JSON.parseArray(r, ApiDefinitionEntity.class); if (entities != null) { for (ApiDefinitionEntity entity : entities) { entity.setApp(app); entity.setIp(ip); entity.setPort(port); } } return entities; }); } catch (Exception ex) { logger.warn("Error when fetching gateway apis", ex); return AsyncUtils.newFailedFuture(ex); } } public boolean modifyApis(String app, String ip, int port, List apis) { if (apis == null) { return true; } try { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); String data = JSON.toJSONString( apis.stream().map(r -> r.toApiDefinition()).collect(Collectors.toList())); Map params = new HashMap<>(2); params.put("data", data); String result = executeCommand(app, ip, port, MODIFY_GATEWAY_API_PATH, params, true).get(); logger.info("Modify gateway apis: {}", result); return true; } catch (Exception e) { logger.warn("Error when modifying gateway apis", e); return false; } } public CompletableFuture> fetchGatewayFlowRules(String app, String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { return executeCommand(ip, port, FETCH_GATEWAY_FLOW_RULE_PATH, false) .thenApply(r -> { List gatewayFlowRules = JSON.parseArray(r, GatewayFlowRule.class); List entities = gatewayFlowRules.stream().map(rule -> GatewayFlowRuleEntity.fromGatewayFlowRule(app, ip, port, rule)).collect(Collectors.toList()); return entities; }); } catch (Exception ex) { logger.warn("Error when fetching gateway flow rules", ex); return AsyncUtils.newFailedFuture(ex); } } public boolean modifyGatewayFlowRules(String app, String ip, int port, List rules) { if (rules == null) { return true; } try { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); String data = JSON.toJSONString( rules.stream().map(r -> r.toGatewayFlowRule()).collect(Collectors.toList())); Map params = new HashMap<>(2); params.put("data", data); String result = executeCommand(app, ip, port, MODIFY_GATEWAY_FLOW_RULE_PATH, params, true).get(); logger.info("Modify gateway flow rules: {}", result); return true; } catch (Exception e) { logger.warn("Error when modifying gateway apis", e); return false; } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/config/AuthConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import com.alibaba.csp.sentinel.dashboard.auth.*; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.http.HttpServletRequest; @Configuration @EnableConfigurationProperties(AuthProperties.class) public class AuthConfiguration { private final AuthProperties authProperties; public AuthConfiguration(AuthProperties authProperties) { this.authProperties = authProperties; } @Bean @ConditionalOnMissingBean public AuthService httpServletRequestAuthService() { if (this.authProperties.isEnabled()) { return new SimpleWebAuthServiceImpl(); } return new FakeAuthServiceImpl(); } @Bean @ConditionalOnMissingBean public LoginAuthenticationFilter loginAuthenticationFilter(AuthService httpServletRequestAuthService) { return new DefaultLoginAuthenticationFilter(httpServletRequestAuthService); } @Bean @ConditionalOnMissingBean public AuthorizationInterceptor authorizationInterceptor(AuthService httpServletRequestAuthService) { return new DefaultAuthorizationInterceptor(httpServletRequestAuthService); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/config/AuthProperties.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "auth") public class AuthProperties { private boolean enabled = true; public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/config/DashboardConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.springframework.lang.NonNull; /** *

          Dashboard local config support.

          *

          * Dashboard supports configuration loading by several ways by order:
          * 1. System.properties
          * 2. Env *

          * * @author jason * @since 1.5.0 */ public class DashboardConfig { public static final int DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS = 60_000; /** * Login username */ public static final String CONFIG_AUTH_USERNAME = "sentinel.dashboard.auth.username"; /** * Login password */ public static final String CONFIG_AUTH_PASSWORD = "sentinel.dashboard.auth.password"; /** * Hide application name in sidebar when it has no healthy machines after specific period in millisecond. */ public static final String CONFIG_HIDE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.app.hideAppNoMachineMillis"; /** * Remove application when it has no healthy machines after specific period in millisecond. */ public static final String CONFIG_REMOVE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.removeAppNoMachineMillis"; /** * Timeout */ public static final String CONFIG_UNHEALTHY_MACHINE_MILLIS = "sentinel.dashboard.unhealthyMachineMillis"; /** * Auto remove unhealthy machine after specific period in millisecond. */ public static final String CONFIG_AUTO_REMOVE_MACHINE_MILLIS = "sentinel.dashboard.autoRemoveMachineMillis"; private static final ConcurrentMap cacheMap = new ConcurrentHashMap<>(); @NonNull private static String getConfig(String name) { // env String val = System.getenv(name); if (StringUtils.isNotEmpty(val)) { return val; } // properties val = System.getProperty(name); if (StringUtils.isNotEmpty(val)) { return val; } return ""; } protected static String getConfigStr(String name) { if (cacheMap.containsKey(name)) { return (String) cacheMap.get(name); } String val = getConfig(name); if (StringUtils.isBlank(val)) { return null; } cacheMap.put(name, val); return val; } protected static int getConfigInt(String name, int defaultVal, int minVal) { if (cacheMap.containsKey(name)) { return (int)cacheMap.get(name); } int val = NumberUtils.toInt(getConfig(name)); if (val == 0) { val = defaultVal; } else if (val < minVal) { val = minVal; } cacheMap.put(name, val); return val; } public static String getAuthUsername() { return getConfigStr(CONFIG_AUTH_USERNAME); } public static String getAuthPassword() { return getConfigStr(CONFIG_AUTH_PASSWORD); } public static int getHideAppNoMachineMillis() { return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000); } public static int getRemoveAppNoMachineMillis() { return getConfigInt(CONFIG_REMOVE_APP_NO_MACHINE_MILLIS, 0, 120000); } public static int getAutoRemoveMachineMillis() { return getConfigInt(CONFIG_AUTO_REMOVE_MACHINE_MILLIS, 0, 300000); } public static int getUnhealthyMachineMillis() { return getConfigInt(CONFIG_UNHEALTHY_MACHINE_MILLIS, DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS, 30000); } public static void clearCache() { cacheMap.clear(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/config/WebConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; import com.alibaba.csp.sentinel.dashboard.auth.AuthorizationInterceptor; import com.alibaba.csp.sentinel.dashboard.auth.LoginAuthenticationFilter; import com.alibaba.csp.sentinel.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.PostConstruct; import javax.servlet.Filter; /** * @author leyou */ @Configuration public class WebConfig implements WebMvcConfigurer { private final Logger logger = LoggerFactory.getLogger(WebConfig.class); @Autowired private LoginAuthenticationFilter loginAuthenticationFilter; @Autowired private AuthorizationInterceptor authorizationInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/"); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:/index.htm"); } /** * Add {@link CommonFilter} to the server, this is the simplest way to use Sentinel * for Web application. */ @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.setName("sentinelFilter"); registration.setOrder(1); // If this is enabled, the entrance of all Web URL resources will be unified as a single context name. // In most scenarios that's enough, and it could reduce the memory footprint. registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "true"); logger.info("Sentinel servlet CommonFilter registered"); return registration; } @PostConstruct public void doInit() { Set suffixSet = new HashSet<>(Arrays.asList(".js", ".css", ".html", ".ico", ".txt", ".woff", ".woff2")); // Example: register a UrlCleaner to exclude URLs of common static resources. WebCallbackManager.setUrlCleaner(url -> { if (StringUtil.isEmpty(url)) { return url; } if (suffixSet.stream().anyMatch(url::endsWith)) { return null; } return url; }); } @Bean public FilterRegistrationBean authenticationFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(loginAuthenticationFilter); registration.addUrlPatterns("/*"); registration.setName("authenticationFilter"); registration.setOrder(0); return registration; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/AppController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.servlet.http.HttpServletRequest; import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.MachineInfoVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author Carpenter Lee */ @RestController @RequestMapping(value = "/app") public class AppController { @Autowired private AppManagement appManagement; @GetMapping("/names.json") public Result> queryApps(HttpServletRequest request) { return Result.ofSuccess(appManagement.getAppNames()); } @GetMapping("/briefinfos.json") public Result> queryAppInfos(HttpServletRequest request) { List list = new ArrayList<>(appManagement.getBriefApps()); Collections.sort(list, Comparator.comparing(AppInfo::getApp)); return Result.ofSuccess(list); } @GetMapping(value = "/{app}/machines.json") public Result> getMachinesByApp(@PathVariable("app") String app) { AppInfo appInfo = appManagement.getDetailApp(app); if (appInfo == null) { return Result.ofSuccess(null); } List list = new ArrayList<>(appInfo.getMachines()); Collections.sort(list, Comparator.comparing(MachineInfo::getApp).thenComparing(MachineInfo::getIp).thenComparingInt(MachineInfo::getPort)); return Result.ofSuccess(MachineInfoVo.fromMachineInfoList(list)); } @RequestMapping(value = "/{app}/machine/remove.json") public Result removeMachineById( @PathVariable("app") String app, @RequestParam(name = "ip") String ip, @RequestParam(name = "port") int port) { AppInfo appInfo = appManagement.getDetailApp(app); if (appInfo == null) { return Result.ofSuccess(null); } if (appManagement.removeMachine(app, ip, port)) { return Result.ofSuccessMsg("success"); } else { return Result.ofFail(1, "remove failed"); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/AuthController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.auth.SimpleWebAuthServiceImpl; import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; /** * @author cdfive * @since 1.6.0 */ @RestController @RequestMapping("/auth") public class AuthController { private static final Logger LOGGER = LoggerFactory.getLogger(AuthController.class); @Value("${auth.username:sentinel}") private String authUsername; @Value("${auth.password:sentinel}") private String authPassword; @Autowired private AuthService authService; @PostMapping("/login") public Result login(HttpServletRequest request, String username, String password) { if (StringUtils.isNotBlank(DashboardConfig.getAuthUsername())) { authUsername = DashboardConfig.getAuthUsername(); } if (StringUtils.isNotBlank(DashboardConfig.getAuthPassword())) { authPassword = DashboardConfig.getAuthPassword(); } /* * If auth.username or auth.password is blank(set in application.properties or VM arguments), * auth will pass, as the front side validate the input which can't be blank, * so user can input any username or password(both are not blank) to login in that case. */ if (StringUtils.isNotBlank(authUsername) && !authUsername.equals(username) || StringUtils.isNotBlank(authPassword) && !authPassword.equals(password)) { LOGGER.error("Login failed: Invalid username or password, username=" + username); return Result.ofFail(-1, "Invalid username or password"); } AuthService.AuthUser authUser = new SimpleWebAuthServiceImpl.SimpleWebAuthUserImpl(username); request.getSession().setAttribute(SimpleWebAuthServiceImpl.WEB_SESSION_KEY, authUser); return Result.ofSuccess(authUser); } @PostMapping(value = "/logout") public Result logout(HttpServletRequest request) { request.getSession().invalidate(); return Result.ofSuccess(null); } @PostMapping(value = "/check") public Result check(HttpServletRequest request) { AuthService.AuthUser authUser = authService.getAuthUser(request); if (authUser == null) { return Result.ofFail(-1, "Not logged in"); } return Result.ofSuccess(authUser); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/AuthorityRuleController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao * @since 0.2.1 */ @RestController @RequestMapping(value = "/authority") public class AuthorityRuleController { private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class); @Autowired private SentinelApiClient sentinelApiClient; @Autowired private RuleRepository repository; @Autowired private AppManagement appManagement; @GetMapping("/rules") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryAllRulesForMachine(@RequestParam String app, @RequestParam String ip, @RequestParam Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip cannot be null or empty"); } if (port == null || port <= 0) { return Result.ofFail(-1, "Invalid parameter: port"); } if (!appManagement.isValidMachineOfApp(app, ip)) { return Result.ofFail(-1, "given ip does not belong to given app"); } try { List rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("Error when querying authority rules", throwable); return Result.ofFail(-1, throwable.getMessage()); } } private Result checkEntityInternal(AuthorityRuleEntity entity) { if (entity == null) { return Result.ofFail(-1, "bad rule body"); } if (StringUtil.isBlank(entity.getApp())) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isBlank(entity.getIp())) { return Result.ofFail(-1, "ip can't be null or empty"); } if (entity.getPort() == null || entity.getPort() <= 0) { return Result.ofFail(-1, "port can't be null"); } if (entity.getRule() == null) { return Result.ofFail(-1, "rule can't be null"); } if (StringUtil.isBlank(entity.getResource())) { return Result.ofFail(-1, "resource name cannot be null or empty"); } if (StringUtil.isBlank(entity.getLimitApp())) { return Result.ofFail(-1, "limitApp should be valid"); } if (entity.getStrategy() != RuleConstant.AUTHORITY_WHITE && entity.getStrategy() != RuleConstant.AUTHORITY_BLACK) { return Result.ofFail(-1, "Unknown strategy (must be blacklist or whitelist)"); } return null; } @PostMapping("/rule") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiAddAuthorityRule(@RequestBody AuthorityRuleEntity entity) { Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setId(null); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("Failed to add authority rule", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { logger.info("Publish authority rules failed after rule add"); } return Result.ofSuccess(entity); } @PutMapping("/rule/{id}") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiUpdateParamFlowRule(@PathVariable("id") Long id, @RequestBody AuthorityRuleEntity entity) { if (id == null || id <= 0) { return Result.ofFail(-1, "Invalid id"); } Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setId(id); Date date = new Date(); entity.setGmtCreate(null); entity.setGmtModified(date); try { entity = repository.save(entity); if (entity == null) { return Result.ofFail(-1, "Failed to save authority rule"); } } catch (Throwable throwable) { logger.error("Failed to save authority rule", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { logger.info("Publish authority rules failed after rule update"); } return Result.ofSuccess(entity); } @DeleteMapping("/rule/{id}") @AuthAction(PrivilegeType.DELETE_RULE) public Result apiDeleteRule(@PathVariable("id") Long id) { if (id == null) { return Result.ofFail(-1, "id cannot be null"); } AuthorityRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Exception e) { return Result.ofFail(-1, e.getMessage()); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.error("Publish authority rules failed after rule delete"); } return Result.ofSuccess(id); } private boolean publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DegradeController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Controller regarding APIs of degrade rules. Refactored since 1.8.0. * * @author Carpenter Lee * @author Eric Zhao */ @RestController @RequestMapping("/degrade") public class DegradeController { private final Logger logger = LoggerFactory.getLogger(DegradeController.class); @Autowired private RuleRepository repository; @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; @GetMapping("/rules.json") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryMachineRules(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } if (!appManagement.isValidMachineOfApp(app, ip)) { return Result.ofFail(-1, "given ip does not belong to given app"); } try { List rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("queryApps error:", throwable); return Result.ofThrowable(-1, throwable); } } @PostMapping("/rule") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiAddRule(@RequestBody DegradeRuleEntity entity) { Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable t) { logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t); return Result.ofThrowable(-1, t); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { logger.warn("Publish degrade rules failed, app={}", entity.getApp()); } return Result.ofSuccess(entity); } @PutMapping("/rule/{id}") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiUpdateRule(@PathVariable("id") Long id, @RequestBody DegradeRuleEntity entity) { if (id == null || id <= 0) { return Result.ofFail(-1, "id can't be null or negative"); } DegradeRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofFail(-1, "Degrade rule does not exist, id=" + id); } entity.setApp(oldEntity.getApp()); entity.setIp(oldEntity.getIp()); entity.setPort(oldEntity.getPort()); entity.setId(oldEntity.getId()); Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setGmtCreate(oldEntity.getGmtCreate()); entity.setGmtModified(new Date()); try { entity = repository.save(entity); } catch (Throwable t) { logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t); return Result.ofThrowable(-1, t); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { logger.warn("Publish degrade rules failed, app={}", entity.getApp()); } return Result.ofSuccess(entity); } @DeleteMapping("/rule/{id}") @AuthAction(PrivilegeType.DELETE_RULE) public Result delete(@PathVariable("id") Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } DegradeRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("Failed to delete degrade rule, id={}", id, throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp()); } return Result.ofSuccess(id); } private boolean publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules); } private Result checkEntityInternal(DegradeRuleEntity entity) { if (StringUtil.isBlank(entity.getApp())) { return Result.ofFail(-1, "app can't be blank"); } if (StringUtil.isBlank(entity.getIp())) { return Result.ofFail(-1, "ip can't be null or empty"); } if (!appManagement.isValidMachineOfApp(entity.getApp(), entity.getIp())) { return Result.ofFail(-1, "given ip does not belong to given app"); } if (entity.getPort() == null || entity.getPort() <= 0) { return Result.ofFail(-1, "invalid port: " + entity.getPort()); } if (StringUtil.isBlank(entity.getLimitApp())) { return Result.ofFail(-1, "limitApp can't be null or empty"); } if (StringUtil.isBlank(entity.getResource())) { return Result.ofFail(-1, "resource can't be null or empty"); } Double threshold = entity.getCount(); if (threshold == null || threshold < 0) { return Result.ofFail(-1, "invalid threshold: " + threshold); } Integer recoveryTimeoutSec = entity.getTimeWindow(); if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) { return Result.ofFail(-1, "recoveryTimeout should be positive"); } Integer strategy = entity.getGrade(); if (strategy == null) { return Result.ofFail(-1, "circuit breaker strategy cannot be null"); } if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType() || strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy); } if (entity.getMinRequestAmount() == null || entity.getMinRequestAmount() <= 0) { return Result.ofFail(-1, "Invalid minRequestAmount"); } if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) { return Result.ofFail(-1, "Invalid statInterval"); } if (strategy == RuleConstant.DEGRADE_GRADE_RT) { Double slowRatio = entity.getSlowRatioThreshold(); if (slowRatio == null) { return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy"); } else if (slowRatio < 0 || slowRatio > 1) { return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]"); } } else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) { if (threshold > 1) { return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]"); } } return null; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DemoController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Random; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; @Controller @RequestMapping(value = "/demo", produces = MediaType.APPLICATION_JSON_VALUE) public class DemoController { Logger logger = LoggerFactory.getLogger(MachineRegistryController.class); @RequestMapping("/greeting") public String greeting() { return "index"; } @RequestMapping("/link") @ResponseBody public String link() throws BlockException { Entry entry = SphU.entry("head1", EntryType.IN); Entry entry1 = SphU.entry("head2", EntryType.IN); Entry entry2 = SphU.entry("head3", EntryType.IN); Entry entry3 = SphU.entry("head4", EntryType.IN); entry3.exit(); entry2.exit(); entry1.exit(); entry.exit(); return "successfully create a call link"; } @RequestMapping("/loop") @ResponseBody public String loop(String name, int time) throws BlockException { for (int i = 0; i < 10; i++) { Thread timer = new Thread(new RunTask(name, time, false)); timer.setName("false"); timer.start(); } return "successfully create a loop thread"; } @RequestMapping("/slow") @ResponseBody public String slow(String name, int time) throws BlockException { for (int i = 0; i < 10; i++) { Thread timer = new Thread(new RunTask(name, time, true)); timer.setName("false"); timer.start(); } return "successfully create a loop thread"; } static class RunTask implements Runnable { int time; boolean stop = false; String name; boolean slow = false; public RunTask(String name, int time, boolean slow) { super(); this.time = time; this.name = name; this.slow = slow; } @Override public void run() { long startTime = System.currentTimeMillis(); ContextUtil.enter(String.valueOf(startTime)); while (!stop) { long now = System.currentTimeMillis(); if (now - startTime > time * 1000) { stop = true; } Entry e1 = null; try { e1 = SphU.entry(name); if (slow) { TimeUnit.MILLISECONDS.sleep(3000); } } catch (Exception e) { } finally { if (e1 != null) { e1.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(200)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ContextUtil.exit(); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/FlowControllerV1.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Flow rule controller. * * @author leyou * @author Eric Zhao */ @RestController @RequestMapping(value = "/v1/flow") public class FlowControllerV1 { private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class); @Autowired private InMemoryRuleRepositoryAdapter repository; @Autowired private AppManagement appManagement; @Autowired private SentinelApiClient sentinelApiClient; @GetMapping("/rules") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryMachineRules(@RequestParam String app, @RequestParam String ip, @RequestParam Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } if (!appManagement.isValidMachineOfApp(app, ip)) { return Result.ofFail(-1, "given ip does not belong to given app"); } try { List rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("Error when querying flow rules", throwable); return Result.ofThrowable(-1, throwable); } } private Result checkEntityInternal(FlowRuleEntity entity) { if (StringUtil.isBlank(entity.getApp())) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isBlank(entity.getIp())) { return Result.ofFail(-1, "ip can't be null or empty"); } if (entity.getPort() == null) { return Result.ofFail(-1, "port can't be null"); } if (!appManagement.isValidMachineOfApp(entity.getApp(), entity.getIp())) { return Result.ofFail(-1, "given ip does not belong to given app"); } if (StringUtil.isBlank(entity.getLimitApp())) { return Result.ofFail(-1, "limitApp can't be null or empty"); } if (StringUtil.isBlank(entity.getResource())) { return Result.ofFail(-1, "resource can't be null or empty"); } if (entity.getGrade() == null) { return Result.ofFail(-1, "grade can't be null"); } if (entity.getGrade() != 0 && entity.getGrade() != 1) { return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got"); } if (entity.getCount() == null || entity.getCount() < 0) { return Result.ofFail(-1, "count should be at lease zero"); } if (entity.getStrategy() == null) { return Result.ofFail(-1, "strategy can't be null"); } if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) { return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0"); } if (entity.getControlBehavior() == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } int controlBehavior = entity.getControlBehavior(); if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) { return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1"); } if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) { return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2"); } if (entity.isClusterMode() && entity.getClusterConfig() == null) { return Result.ofFail(-1, "cluster config should be valid"); } return null; } @PostMapping("/rule") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiAddFlowRule(@RequestBody FlowRuleEntity entity) { Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setId(null); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); entity.setLimitApp(entity.getLimitApp().trim()); entity.setResource(entity.getResource().trim()); try { entity = repository.save(entity); publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS); return Result.ofSuccess(entity); } catch (Throwable t) { Throwable e = t instanceof ExecutionException ? t.getCause() : t; logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e); return Result.ofFail(-1, e.getMessage()); } } @PutMapping("/save.json") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiUpdateFlowRule(Long id, String app, String limitApp, String resource, Integer grade, Double count, Integer strategy, String refResource, Integer controlBehavior, Integer warmUpPeriodSec, Integer maxQueueingTimeMs) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } FlowRuleEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "id " + id + " dose not exist"); } if (StringUtil.isNotBlank(app)) { entity.setApp(app.trim()); } if (StringUtil.isNotBlank(limitApp)) { entity.setLimitApp(limitApp.trim()); } if (StringUtil.isNotBlank(resource)) { entity.setResource(resource.trim()); } if (grade != null) { if (grade != 0 && grade != 1) { return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got"); } entity.setGrade(grade); } if (count != null) { entity.setCount(count); } if (strategy != null) { if (strategy != 0 && strategy != 1 && strategy != 2) { return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got"); } entity.setStrategy(strategy); if (strategy != 0) { if (StringUtil.isBlank(refResource)) { return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0"); } entity.setRefResource(refResource.trim()); } } if (controlBehavior != null) { if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) { return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got"); } if (controlBehavior == 1 && warmUpPeriodSec == null) { return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1"); } if (controlBehavior == 2 && maxQueueingTimeMs == null) { return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2"); } entity.setControlBehavior(controlBehavior); if (warmUpPeriodSec != null) { entity.setWarmUpPeriodSec(warmUpPeriodSec); } if (maxQueueingTimeMs != null) { entity.setMaxQueueingTimeMs(maxQueueingTimeMs); } } Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); if (entity == null) { return Result.ofFail(-1, "save entity fail: null"); } publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS); return Result.ofSuccess(entity); } catch (Throwable t) { Throwable e = t instanceof ExecutionException ? t.getCause() : t; logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(), entity.getIp(), id, e); return Result.ofFail(-1, e.getMessage()); } } @DeleteMapping("/delete.json") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiDeleteFlowRule(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } FlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Exception e) { return Result.ofFail(-1, e.getMessage()); } try { publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(5000, TimeUnit.MILLISECONDS); return Result.ofSuccess(id); } catch (Throwable t) { Throwable e = t instanceof ExecutionException ? t.getCause() : t; logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(), oldEntity.getIp(), id, e); return Result.ofFail(-1, e.getMessage()); } } private CompletableFuture publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/MachineRegistryController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.apache.http.conn.util.InetAddressUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping(value = "/registry", produces = MediaType.APPLICATION_JSON_VALUE) public class MachineRegistryController { private final Logger logger = LoggerFactory.getLogger(MachineRegistryController.class); @Autowired private AppManagement appManagement; @ResponseBody @RequestMapping("/machine") public Result receiveHeartBeat(String app, @RequestParam(value = "app_type", required = false, defaultValue = "0") Integer appType, Long version, String v, String hostname, String ip, Integer port) { if (StringUtil.isBlank(app) || app.length() > 256) { return Result.ofFail(-1, "invalid appName"); } if (StringUtil.isBlank(ip) || ip.length() > 128) { return Result.ofFail(-1, "invalid ip: " + ip); } if (!InetAddressUtils.isIPv4Address(ip) && !InetAddressUtils.isIPv6Address(ip)) { return Result.ofFail(-1, "invalid ip: " + ip); } if (port == null || port < -1) { return Result.ofFail(-1, "invalid port"); } if (hostname != null && hostname.length() > 256) { return Result.ofFail(-1, "hostname too long"); } if (port == -1) { logger.warn("Receive heartbeat from " + ip + " but port not set yet"); return Result.ofFail(-1, "your port not set yet"); } String sentinelVersion = StringUtil.isBlank(v) ? "unknown" : v; version = version == null ? System.currentTimeMillis() : version; try { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp(app); machineInfo.setAppType(appType); machineInfo.setHostname(hostname); machineInfo.setIp(ip); machineInfo.setPort(port); machineInfo.setHeartbeatVersion(version); machineInfo.setLastHeartbeat(System.currentTimeMillis()); machineInfo.setVersion(sentinelVersion); appManagement.addMachine(machineInfo); return Result.ofSuccessMsg("success"); } catch (Exception e) { logger.error("Receive heartbeat error", e); return Result.ofFail(-1, e.getMessage()); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/MetricController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; import com.alibaba.csp.sentinel.dashboard.domain.vo.MetricVo; /** * @author leyou */ @Controller @RequestMapping(value = "/metric", produces = MediaType.APPLICATION_JSON_VALUE) public class MetricController { private static Logger logger = LoggerFactory.getLogger(MetricController.class); private static final long maxQueryIntervalMs = 1000 * 60 * 60; @Autowired private MetricsRepository metricStore; @ResponseBody @RequestMapping("/queryTopResourceMetric.json") public Result queryTopResourceMetric(final String app, Integer pageIndex, Integer pageSize, Boolean desc, Long startTime, Long endTime, String searchKey) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (pageIndex == null || pageIndex <= 0) { pageIndex = 1; } if (pageSize == null) { pageSize = 6; } if (pageSize >= 20) { pageSize = 20; } if (desc == null) { desc = true; } if (endTime == null) { endTime = System.currentTimeMillis(); } if (startTime == null) { startTime = endTime - 1000 * 60 * 5; } if (endTime - startTime > maxQueryIntervalMs) { return Result.ofFail(-1, "time intervalMs is too big, must <= 1h"); } List resources = metricStore.listResourcesOfApp(app); logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size()); if (resources == null || resources.isEmpty()) { return Result.ofSuccess(null); } if (!desc) { Collections.reverse(resources); } if (StringUtil.isNotEmpty(searchKey)) { List searched = new ArrayList<>(); for (String resource : resources) { if (resource.contains(searchKey)) { searched.add(resource); } } resources = searched; } int totalPage = (resources.size() + pageSize - 1) / pageSize; List topResource = new ArrayList<>(); if (pageIndex <= totalPage) { topResource = resources.subList((pageIndex - 1) * pageSize, Math.min(pageIndex * pageSize, resources.size())); } final Map> map = new ConcurrentHashMap<>(); logger.debug("topResource={}", topResource); long time = System.currentTimeMillis(); for (final String resource : topResource) { List entities = metricStore.queryByAppAndResourceBetween( app, resource, startTime, endTime); logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size()); List vos = MetricVo.fromMetricEntities(entities, resource); Iterable vosSorted = sortMetricVoAndDistinct(vos); map.put(resource, vosSorted); } logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time); Map resultMap = new HashMap<>(16); resultMap.put("totalCount", resources.size()); resultMap.put("totalPage", totalPage); resultMap.put("pageIndex", pageIndex); resultMap.put("pageSize", pageSize); Map> map2 = new LinkedHashMap<>(); // order matters. for (String identity : topResource) { map2.put(identity, map.get(identity)); } resultMap.put("metric", map2); return Result.ofSuccess(resultMap); } @ResponseBody @RequestMapping("/queryByAppAndResource.json") public Result queryByAppAndResource(String app, String identity, Long startTime, Long endTime) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(identity)) { return Result.ofFail(-1, "identity can't be null or empty"); } if (endTime == null) { endTime = System.currentTimeMillis(); } if (startTime == null) { startTime = endTime - 1000 * 60; } if (endTime - startTime > maxQueryIntervalMs) { return Result.ofFail(-1, "time intervalMs is too big, must <= 1h"); } List entities = metricStore.queryByAppAndResourceBetween( app, identity, startTime, endTime); List vos = MetricVo.fromMetricEntities(entities, identity); return Result.ofSuccess(sortMetricVoAndDistinct(vos)); } private Iterable sortMetricVoAndDistinct(List vos) { if (vos == null) { return null; } Map map = new TreeMap<>(); for (MetricVo vo : vos) { MetricVo oldVo = map.get(vo.getTimestamp()); if (oldVo == null || vo.getGmtCreate() > oldVo.getGmtCreate()) { map.put(vo.getTimestamp(), vo); } } return map.values(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/ParamFlowRuleController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import com.alibaba.csp.sentinel.dashboard.util.VersionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao * @since 0.2.1 */ @RestController @RequestMapping(value = "/paramFlow") public class ParamFlowRuleController { private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class); @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; @Autowired private RuleRepository repository; private boolean checkIfSupported(String app, String ip, int port) { try { return Optional.ofNullable(appManagement.getDetailApp(app)) .flatMap(e -> e.getMachine(ip, port)) .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) .map(v -> v.greaterOrEqual(version020))) .orElse(true); // If error occurred or cannot retrieve machine info, return true. } catch (Exception ex) { return true; } } @GetMapping("/rules") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryAllRulesForMachine(@RequestParam String app, @RequestParam String ip, @RequestParam Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip cannot be null or empty"); } if (port == null || port <= 0) { return Result.ofFail(-1, "Invalid parameter: port"); } if (!appManagement.isValidMachineOfApp(app, ip)) { return Result.ofFail(-1, "given ip does not belong to given app"); } if (!checkIfSupported(app, ip, port)) { return unsupportedVersion(); } try { return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port) .thenApply(repository::saveAll) .thenApply(Result::ofSuccess) .get(); } catch (ExecutionException ex) { logger.error("Error when querying parameter flow rules", ex.getCause()); if (isNotSupported(ex.getCause())) { return unsupportedVersion(); } else { return Result.ofThrowable(-1, ex.getCause()); } } catch (Throwable throwable) { logger.error("Error when querying parameter flow rules", throwable); return Result.ofFail(-1, throwable.getMessage()); } } private boolean isNotSupported(Throwable ex) { return ex instanceof CommandNotFoundException; } @PostMapping("/rule") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) { Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) { return unsupportedVersion(); } entity.setId(null); entity.getRule().setResource(entity.getResource().trim()); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(); return Result.ofSuccess(entity); } catch (ExecutionException ex) { logger.error("Error when adding new parameter flow rules", ex.getCause()); if (isNotSupported(ex.getCause())) { return unsupportedVersion(); } else { return Result.ofThrowable(-1, ex.getCause()); } } catch (Throwable throwable) { logger.error("Error when adding new parameter flow rules", throwable); return Result.ofFail(-1, throwable.getMessage()); } } private Result checkEntityInternal(ParamFlowRuleEntity entity) { if (entity == null) { return Result.ofFail(-1, "bad rule body"); } if (StringUtil.isBlank(entity.getApp())) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isBlank(entity.getIp())) { return Result.ofFail(-1, "ip can't be null or empty"); } if (entity.getPort() == null || entity.getPort() <= 0) { return Result.ofFail(-1, "port can't be null"); } if (entity.getRule() == null) { return Result.ofFail(-1, "rule can't be null"); } if (StringUtil.isBlank(entity.getResource())) { return Result.ofFail(-1, "resource name cannot be null or empty"); } if (entity.getCount() < 0) { return Result.ofFail(-1, "count should be valid"); } if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) { return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control"); } if (entity.getParamIdx() == null || entity.getParamIdx() < 0) { return Result.ofFail(-1, "paramIdx should be valid"); } if (entity.getDurationInSec() <= 0) { return Result.ofFail(-1, "durationInSec should be valid"); } if (entity.getControlBehavior() < 0) { return Result.ofFail(-1, "controlBehavior should be valid"); } return null; } @PutMapping("/rule/{id}") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result apiUpdateParamFlowRule(@PathVariable("id") Long id, @RequestBody ParamFlowRuleEntity entity) { if (id == null || id <= 0) { return Result.ofFail(-1, "Invalid id"); } ParamFlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofFail(-1, "id " + id + " does not exist"); } Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) { return unsupportedVersion(); } entity.setId(id); Date date = new Date(); entity.setGmtCreate(oldEntity.getGmtCreate()); entity.setGmtModified(date); try { entity = repository.save(entity); publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(); return Result.ofSuccess(entity); } catch (ExecutionException ex) { logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause()); if (isNotSupported(ex.getCause())) { return unsupportedVersion(); } else { return Result.ofThrowable(-1, ex.getCause()); } } catch (Throwable throwable) { logger.error("Error when updating parameter flow rules, id=" + id, throwable); return Result.ofFail(-1, throwable.getMessage()); } } @DeleteMapping("/rule/{id}") @AuthAction(PrivilegeType.DELETE_RULE) public Result apiDeleteRule(@PathVariable("id") Long id) { if (id == null) { return Result.ofFail(-1, "id cannot be null"); } ParamFlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(); return Result.ofSuccess(id); } catch (ExecutionException ex) { logger.error("Error when deleting parameter flow rules", ex.getCause()); if (isNotSupported(ex.getCause())) { return unsupportedVersion(); } else { return Result.ofThrowable(-1, ex.getCause()); } } catch (Throwable throwable) { logger.error("Error when deleting parameter flow rules", throwable); return Result.ofFail(-1, throwable.getMessage()); } } private CompletableFuture publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules); } private Result unsupportedVersion() { return Result.ofFail(4041, "Sentinel client not supported for parameter flow control (unsupported version or dependency absent)"); } private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/ResourceController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.List; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.dashboard.domain.ResourceTreeNode; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.ResourceVo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Carpenter Lee */ @RestController @RequestMapping(value = "/resource") public class ResourceController { private static Logger logger = LoggerFactory.getLogger(ResourceController.class); @Autowired private SentinelApiClient httpFetcher; /** * Fetch real time statistics info of the machine. * * @param ip ip to fetch * @param port port of the ip * @param type one of [root, default, cluster], 'root' means fetching from tree root node, 'default' means * fetching from tree default node, 'cluster' means fetching from cluster node. * @param searchKey key to search * @return node statistics info. */ @GetMapping("/machineResource.json") public Result> fetchResourceChainListOfMachine(String ip, Integer port, String type, String searchKey) { if (StringUtil.isEmpty(ip) || port == null) { return Result.ofFail(-1, "invalid param, give ip, port"); } final String ROOT = "root"; final String DEFAULT = "default"; if (StringUtil.isEmpty(type)) { type = ROOT; } if (ROOT.equalsIgnoreCase(type) || DEFAULT.equalsIgnoreCase(type)) { List nodeVos = httpFetcher.fetchResourceOfMachine(ip, port, type); if (nodeVos == null) { return Result.ofSuccess(null); } ResourceTreeNode treeNode = ResourceTreeNode.fromNodeVoList(nodeVos); treeNode.searchIgnoreCase(searchKey); return Result.ofSuccess(ResourceVo.fromResourceTreeNode(treeNode)); } else { // Normal (cluster node). List nodeVos = httpFetcher.fetchClusterNodeOfMachine(ip, port, true); if (nodeVos == null) { return Result.ofSuccess(null); } if (StringUtil.isNotEmpty(searchKey)) { nodeVos = nodeVos.stream().filter(node -> node.getResource() .toLowerCase().contains(searchKey.toLowerCase())) .collect(Collectors.toList()); } return Result.ofSuccess(ResourceVo.fromNodeVoList(nodeVos)); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/SystemController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author leyou(lihao) */ @RestController @RequestMapping("/system") public class SystemController { private final Logger logger = LoggerFactory.getLogger(SystemController.class); @Autowired private RuleRepository repository; @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; private Result checkBasicParams(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } if (!appManagement.isValidMachineOfApp(app, ip)) { return Result.ofFail(-1, "given ip does not belong to given app"); } if (port <= 0 || port > 65535) { return Result.ofFail(-1, "port should be in (0, 65535)"); } return null; } @GetMapping("/rules.json") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryMachineRules(String app, String ip, Integer port) { Result> checkResult = checkBasicParams(app, ip, port); if (checkResult != null) { return checkResult; } try { List rules = sentinelApiClient.fetchSystemRuleOfMachine(app, ip, port); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("Query machine system rules error", throwable); return Result.ofThrowable(-1, throwable); } } private int countNotNullAndNotNegative(Number... values) { int notNullCount = 0; for (int i = 0; i < values.length; i++) { if (values[i] != null && values[i].doubleValue() >= 0) { notNullCount++; } } return notNullCount; } @RequestMapping("/new.json") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiAdd(String app, String ip, Integer port, Double highestSystemLoad, Double highestCpuUsage, Long avgRt, Long maxThread, Double qps) { Result checkResult = checkBasicParams(app, ip, port); if (checkResult != null) { return checkResult; } int notNullCount = countNotNullAndNotNegative(highestSystemLoad, avgRt, maxThread, qps, highestCpuUsage); if (notNullCount != 1) { return Result.ofFail(-1, "only one of [highestSystemLoad, avgRt, maxThread, qps,highestCpuUsage] " + "value must be set > 0, but " + notNullCount + " values get"); } if (null != highestCpuUsage && highestCpuUsage > 1) { return Result.ofFail(-1, "highestCpuUsage must between [0.0, 1.0]"); } SystemRuleEntity entity = new SystemRuleEntity(); entity.setApp(app.trim()); entity.setIp(ip.trim()); entity.setPort(port); // -1 is a fake value if (null != highestSystemLoad) { entity.setHighestSystemLoad(highestSystemLoad); } else { entity.setHighestSystemLoad(-1D); } if (null != highestCpuUsage) { entity.setHighestCpuUsage(highestCpuUsage); } else { entity.setHighestCpuUsage(-1D); } if (avgRt != null) { entity.setAvgRt(avgRt); } else { entity.setAvgRt(-1L); } if (maxThread != null) { entity.setMaxThread(maxThread); } else { entity.setMaxThread(-1L); } if (qps != null) { entity.setQps(qps); } else { entity.setQps(-1D); } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("Add SystemRule error", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(app, ip, port)) { logger.warn("Publish system rules fail after rule add"); } return Result.ofSuccess(entity); } @GetMapping("/save.json") @AuthAction(PrivilegeType.WRITE_RULE) public Result apiUpdateIfNotNull(Long id, String app, Double highestSystemLoad, Double highestCpuUsage, Long avgRt, Long maxThread, Double qps) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } SystemRuleEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "id " + id + " dose not exist"); } if (StringUtil.isNotBlank(app)) { entity.setApp(app.trim()); } if (highestSystemLoad != null) { if (highestSystemLoad < 0) { return Result.ofFail(-1, "highestSystemLoad must >= 0"); } entity.setHighestSystemLoad(highestSystemLoad); } if (highestCpuUsage != null) { if (highestCpuUsage < 0) { return Result.ofFail(-1, "highestCpuUsage must >= 0"); } if (highestCpuUsage > 1) { return Result.ofFail(-1, "highestCpuUsage must <= 1"); } entity.setHighestCpuUsage(highestCpuUsage); } if (avgRt != null) { if (avgRt < 0) { return Result.ofFail(-1, "avgRt must >= 0"); } entity.setAvgRt(avgRt); } if (maxThread != null) { if (maxThread < 0) { return Result.ofFail(-1, "maxThread must >= 0"); } entity.setMaxThread(maxThread); } if (qps != null) { if (qps < 0) { return Result.ofFail(-1, "qps must >= 0"); } entity.setQps(qps); } Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("save error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { logger.info("publish system rules fail after rule update"); } return Result.ofSuccess(entity); } @RequestMapping("/delete.json") @AuthAction(PrivilegeType.DELETE_RULE) public Result delete(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } SystemRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("delete error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.info("publish system rules fail after rule delete"); } return Result.ofSuccess(id); } private boolean publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setSystemRuleOfMachine(app, ip, port, rules); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/VersionController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author hisenyuan * @since 1.7.0 */ @RestController public class VersionController { private static final String VERSION_PATTERN = "-"; @Value("${sentinel.dashboard.version:}") private String sentinelDashboardVersion; @GetMapping("/version") public Result apiGetVersion() { if (StringUtil.isNotBlank(sentinelDashboardVersion)) { String res = sentinelDashboardVersion; if (sentinelDashboardVersion.contains(VERSION_PATTERN)) { res = sentinelDashboardVersion.substring(0, sentinelDashboardVersion.indexOf(VERSION_PATTERN)); } return Result.ofSuccess(res); } else { return Result.ofFail(1, "getVersion failed: empty version"); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/cluster/ClusterAssignController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.cluster; import java.util.Collections; import java.util.Set; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppFullAssignRequest; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppSingleServerAssignRequest; import com.alibaba.csp.sentinel.dashboard.service.ClusterAssignService; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao * @since 1.4.1 */ @RestController @RequestMapping("/cluster/assign") public class ClusterAssignController { private final Logger logger = LoggerFactory.getLogger(ClusterAssignController.class); @Autowired private ClusterAssignService clusterAssignService; @PostMapping("/all_server/{app}") public Result apiAssignAllClusterServersOfApp(@PathVariable String app, @RequestBody ClusterAppFullAssignRequest assignRequest) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (assignRequest == null || assignRequest.getClusterMap() == null || assignRequest.getRemainingList() == null) { return Result.ofFail(-1, "bad request body"); } try { return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, assignRequest.getClusterMap(), assignRequest.getRemainingList())); } catch (Throwable throwable) { logger.error("Error when assigning full cluster servers for app: " + app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } @PostMapping("/single_server/{app}") public Result apiAssignSingleClusterServersOfApp(@PathVariable String app, @RequestBody ClusterAppSingleServerAssignRequest assignRequest) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (assignRequest == null || assignRequest.getClusterMap() == null) { return Result.ofFail(-1, "bad request body"); } try { return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, Collections.singletonList(assignRequest.getClusterMap()), assignRequest.getRemainingList())); } catch (Throwable throwable) { logger.error("Error when assigning single cluster servers for app: " + app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } @PostMapping("/unbind_server/{app}") public Result apiUnbindClusterServersOfApp(@PathVariable String app, @RequestBody Set machineIds) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (machineIds == null || machineIds.isEmpty()) { return Result.ofFail(-1, "bad request body"); } try { return Result.ofSuccess(clusterAssignService.unbindClusterServers(app, machineIds)); } catch (Throwable throwable) { logger.error("Error when unbinding cluster server {} for app <{}>", machineIds, app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/cluster/ClusterConfigController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.cluster; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterClientModifyRequest; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterModifyRequest; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStateVO; import com.alibaba.csp.sentinel.dashboard.service.ClusterConfigService; import com.alibaba.csp.sentinel.dashboard.util.ClusterEntityUtils; import com.alibaba.csp.sentinel.dashboard.util.VersionUtils; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao * @since 1.4.0 */ @RestController @RequestMapping(value = "/cluster") public class ClusterConfigController { private final Logger logger = LoggerFactory.getLogger(ClusterConfigController.class); private final SentinelVersion version140 = new SentinelVersion().setMajorVersion(1).setMinorVersion(4); @Autowired private AppManagement appManagement; @Autowired private ClusterConfigService clusterConfigService; @PostMapping("/config/modify_single") public Result apiModifyClusterConfig(@RequestBody String payload) { if (StringUtil.isBlank(payload)) { return Result.ofFail(-1, "empty request body"); } try { JSONObject body = JSON.parseObject(payload); if (body.containsKey(KEY_MODE)) { int mode = body.getInteger(KEY_MODE); switch (mode) { case ClusterStateManager.CLUSTER_CLIENT: ClusterClientModifyRequest data = JSON.parseObject(payload, ClusterClientModifyRequest.class); Result res = checkValidRequest(data); if (res != null) { return res; } clusterConfigService.modifyClusterClientConfig(data).get(); return Result.ofSuccess(true); case ClusterStateManager.CLUSTER_SERVER: ClusterServerModifyRequest d = JSON.parseObject(payload, ClusterServerModifyRequest.class); Result r = checkValidRequest(d); if (r != null) { return r; } // TODO: bad design here, should refactor! clusterConfigService.modifyClusterServerConfig(d).get(); return Result.ofSuccess(true); default: return Result.ofFail(-1, "invalid mode"); } } return Result.ofFail(-1, "invalid parameter"); } catch (ExecutionException ex) { logger.error("Error when modifying cluster config", ex.getCause()); return errorResponse(ex); } catch (Throwable ex) { logger.error("Error when modifying cluster config", ex); return Result.ofFail(-1, ex.getMessage()); } } private Result errorResponse(ExecutionException ex) { if (isNotSupported(ex.getCause())) { return unsupportedVersion(); } else { return Result.ofThrowable(-1, ex.getCause()); } } @GetMapping("/state_single") public Result apiGetClusterState(@RequestParam String app, @RequestParam String ip, @RequestParam Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip cannot be null or empty"); } if (port == null || port <= 0) { return Result.ofFail(-1, "Invalid parameter: port"); } if (!checkIfSupported(app, ip, port)) { return unsupportedVersion(); } try { return clusterConfigService.getClusterUniversalState(app, ip, port) .thenApply(Result::ofSuccess) .get(); } catch (ExecutionException ex) { logger.error("Error when fetching cluster state", ex.getCause()); return errorResponse(ex); } catch (Throwable throwable) { logger.error("Error when fetching cluster state", throwable); return Result.ofFail(-1, throwable.getMessage()); } } @GetMapping("/server_state/{app}") public Result> apiGetClusterServerStateOfApp(@PathVariable String app) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } try { return clusterConfigService.getClusterUniversalState(app) .thenApply(ClusterEntityUtils::wrapToAppClusterServerState) .thenApply(Result::ofSuccess) .get(); } catch (ExecutionException ex) { logger.error("Error when fetching cluster server state of app: " + app, ex.getCause()); return errorResponse(ex); } catch (Throwable throwable) { logger.error("Error when fetching cluster server state of app: " + app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } @GetMapping("/client_state/{app}") public Result> apiGetClusterClientStateOfApp(@PathVariable String app) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } try { return clusterConfigService.getClusterUniversalState(app) .thenApply(ClusterEntityUtils::wrapToAppClusterClientState) .thenApply(Result::ofSuccess) .get(); } catch (ExecutionException ex) { logger.error("Error when fetching cluster token client state of app: " + app, ex.getCause()); return errorResponse(ex); } catch (Throwable throwable) { logger.error("Error when fetching cluster token client state of app: " + app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } @GetMapping("/state/{app}") public Result> apiGetClusterStateOfApp(@PathVariable String app) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app cannot be null or empty"); } try { return clusterConfigService.getClusterUniversalState(app) .thenApply(Result::ofSuccess) .get(); } catch (ExecutionException ex) { logger.error("Error when fetching cluster state of app: " + app, ex.getCause()); return errorResponse(ex); } catch (Throwable throwable) { logger.error("Error when fetching cluster state of app: " + app, throwable); return Result.ofFail(-1, throwable.getMessage()); } } private boolean isNotSupported(Throwable ex) { return ex instanceof CommandNotFoundException; } private boolean checkIfSupported(String app, String ip, int port) { try { return Optional.ofNullable(appManagement.getDetailApp(app)) .flatMap(e -> e.getMachine(ip, port)) .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) .map(v -> v.greaterOrEqual(version140))) .orElse(true); // If error occurred or cannot retrieve machine info, return true. } catch (Exception ex) { return true; } } private Result checkValidRequest(ClusterModifyRequest request) { if (StringUtil.isEmpty(request.getApp())) { return Result.ofFail(-1, "app cannot be empty"); } if (StringUtil.isEmpty(request.getIp())) { return Result.ofFail(-1, "ip cannot be empty"); } if (request.getPort() == null || request.getPort() < 0) { return Result.ofFail(-1, "invalid port"); } if (request.getMode() == null || request.getMode() < 0) { return Result.ofFail(-1, "invalid mode"); } if (!checkIfSupported(request.getApp(), request.getIp(), request.getPort())) { return unsupportedVersion(); } return null; } private Result unsupportedVersion() { return Result.ofFail(4041, "Sentinel client not supported for cluster flow control (unsupported version or dependency absent)"); } private static final String KEY_MODE = "mode"; } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayApiController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore; import com.alibaba.csp.sentinel.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.*; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; /** * Gateway api Controller for manage gateway api definitions. * * @author cdfive * @since 1.7.0 */ @RestController @RequestMapping(value = "/gateway/api") public class GatewayApiController { private final Logger logger = LoggerFactory.getLogger(GatewayApiController.class); @Autowired private InMemApiDefinitionStore repository; @Autowired private SentinelApiClient sentinelApiClient; @GetMapping("/list.json") @AuthAction(AuthService.PrivilegeType.READ_RULE) public Result> queryApis(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } try { List apis = sentinelApiClient.fetchApis(app, ip, port).get(); repository.saveAll(apis); return Result.ofSuccess(apis); } catch (Throwable throwable) { logger.error("queryApis error:", throwable); return Result.ofThrowable(-1, throwable); } } @PostMapping("/new.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } ApiDefinitionEntity entity = new ApiDefinitionEntity(); entity.setApp(app.trim()); String ip = reqVo.getIp(); if (StringUtil.isBlank(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } entity.setIp(ip.trim()); Integer port = reqVo.getPort(); if (port == null) { return Result.ofFail(-1, "port can't be null"); } entity.setPort(port); // API名称 String apiName = reqVo.getApiName(); if (StringUtil.isBlank(apiName)) { return Result.ofFail(-1, "apiName can't be null or empty"); } entity.setApiName(apiName.trim()); // 匹配规则列表 List predicateItems = reqVo.getPredicateItems(); if (CollectionUtils.isEmpty(predicateItems)) { return Result.ofFail(-1, "predicateItems can't empty"); } List predicateItemEntities = new ArrayList<>(); for (ApiPredicateItemVo predicateItem : predicateItems) { ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity(); // 匹配模式 Integer matchStrategy = predicateItem.getMatchStrategy(); if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } predicateItemEntity.setMatchStrategy(matchStrategy); // 匹配串 String pattern = predicateItem.getPattern(); if (StringUtil.isBlank(pattern)) { return Result.ofFail(-1, "pattern can't be null or empty"); } predicateItemEntity.setPattern(pattern); predicateItemEntities.add(predicateItemEntity); } entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities)); // 检查API名称不能重复 List allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port)); if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) { return Result.ofFail(-1, "apiName exists: " + apiName); } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("add gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(app, ip, port)) { logger.warn("publish gateway apis fail after add"); } return Result.ofSuccess(entity); } @PostMapping("/save.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result updateApi(@RequestBody UpdateApiReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } Long id = reqVo.getId(); if (id == null) { return Result.ofFail(-1, "id can't be null"); } ApiDefinitionEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "api does not exist, id=" + id); } // 匹配规则列表 List predicateItems = reqVo.getPredicateItems(); if (CollectionUtils.isEmpty(predicateItems)) { return Result.ofFail(-1, "predicateItems can't empty"); } List predicateItemEntities = new ArrayList<>(); for (ApiPredicateItemVo predicateItem : predicateItems) { ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity(); // 匹配模式 int matchStrategy = predicateItem.getMatchStrategy(); if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy); } predicateItemEntity.setMatchStrategy(matchStrategy); // 匹配串 String pattern = predicateItem.getPattern(); if (StringUtil.isBlank(pattern)) { return Result.ofFail(-1, "pattern can't be null or empty"); } predicateItemEntity.setPattern(pattern); predicateItemEntities.add(predicateItemEntity); } entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities)); Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("update gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(app, entity.getIp(), entity.getPort())) { logger.warn("publish gateway apis fail after update"); } return Result.ofSuccess(entity); } @PostMapping("/delete.json") @AuthAction(AuthService.PrivilegeType.DELETE_RULE) public Result deleteApi(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } ApiDefinitionEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("delete gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.warn("publish gateway apis fail after delete"); } return Result.ofSuccess(id); } private boolean publishApis(String app, String ip, Integer port) { List apis = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.modifyApis(app, ip, port, apis); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore; import com.alibaba.csp.sentinel.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.Date; import java.util.List; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; import static com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity.*; /** * Gateway flow rule Controller for manage gateway flow rules. * * @author cdfive * @since 1.7.0 */ @RestController @RequestMapping(value = "/gateway/flow") public class GatewayFlowRuleController { private final Logger logger = LoggerFactory.getLogger(GatewayFlowRuleController.class); @Autowired private InMemGatewayFlowRuleStore repository; @Autowired private SentinelApiClient sentinelApiClient; @GetMapping("/list.json") @AuthAction(AuthService.PrivilegeType.READ_RULE) public Result> queryFlowRules(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } try { List rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get(); repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("query gateway flow rules error:", throwable); return Result.ofThrowable(-1, throwable); } } @PostMapping("/new.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result addFlowRule(@RequestBody AddFlowRuleReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity(); entity.setApp(app.trim()); String ip = reqVo.getIp(); if (StringUtil.isBlank(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } entity.setIp(ip.trim()); Integer port = reqVo.getPort(); if (port == null) { return Result.ofFail(-1, "port can't be null"); } entity.setPort(port); // API类型, Route ID或API分组 Integer resourceMode = reqVo.getResourceMode(); if (resourceMode == null) { return Result.ofFail(-1, "resourceMode can't be null"); } if (!Arrays.asList(RESOURCE_MODE_ROUTE_ID, RESOURCE_MODE_CUSTOM_API_NAME).contains(resourceMode)) { return Result.ofFail(-1, "invalid resourceMode: " + resourceMode); } entity.setResourceMode(resourceMode); // API名称 String resource = reqVo.getResource(); if (StringUtil.isBlank(resource)) { return Result.ofFail(-1, "resource can't be null or empty"); } entity.setResource(resource.trim()); // 针对请求属性 GatewayParamFlowItemVo paramItem = reqVo.getParamItem(); if (paramItem != null) { GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie Integer parseStrategy = paramItem.getParseStrategy(); if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy); } itemEntity.setParseStrategy(paramItem.getParseStrategy()); // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填 if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { // 参数名称 String fieldName = paramItem.getFieldName(); if (StringUtil.isBlank(fieldName)) { return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); } String pattern = paramItem.getPattern(); // 如果匹配串不为空,验证匹配模式 if (StringUtil.isNotEmpty(pattern)) { itemEntity.setPattern(pattern); Integer matchStrategy = paramItem.getMatchStrategy(); if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } itemEntity.setMatchStrategy(matchStrategy); } } // 阈值类型 0-线程数 1-QPS Integer grade = reqVo.getGrade(); if (grade == null) { return Result.ofFail(-1, "grade can't be null"); } if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) { return Result.ofFail(-1, "invalid grade: " + grade); } entity.setGrade(grade); // QPS阈值 Double count = reqVo.getCount(); if (count == null) { return Result.ofFail(-1, "count can't be null"); } if (count < 0) { return Result.ofFail(-1, "count should be at lease zero"); } entity.setCount(count); // 间隔 Long interval = reqVo.getInterval(); if (interval == null) { return Result.ofFail(-1, "interval can't be null"); } if (interval <= 0) { return Result.ofFail(-1, "interval should be greater than zero"); } entity.setInterval(interval); // 间隔单位 Integer intervalUnit = reqVo.getIntervalUnit(); if (intervalUnit == null) { return Result.ofFail(-1, "intervalUnit can't be null"); } if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) { return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit); } entity.setIntervalUnit(intervalUnit); // 流控方式 0-快速失败 2-匀速排队 Integer controlBehavior = reqVo.getControlBehavior(); if (controlBehavior == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) { return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior); } entity.setControlBehavior(controlBehavior); if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) { // 0-快速失败, 则Burst size必填 Integer burst = reqVo.getBurst(); if (burst == null) { return Result.ofFail(-1, "burst can't be null"); } if (burst < 0) { return Result.ofFail(-1, "invalid burst: " + burst); } entity.setBurst(burst); } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) { // 1-匀速排队, 则超时时间必填 Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs(); if (maxQueueingTimeoutMs == null) { return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null"); } if (maxQueueingTimeoutMs < 0) { return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs); } entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs); } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("add gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(app, ip, port)) { logger.warn("publish gateway flow rules fail after add"); } return Result.ofSuccess(entity); } @PostMapping("/save.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result updateFlowRule(@RequestBody UpdateFlowRuleReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } Long id = reqVo.getId(); if (id == null) { return Result.ofFail(-1, "id can't be null"); } GatewayFlowRuleEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "gateway flow rule does not exist, id=" + id); } // 针对请求属性 GatewayParamFlowItemVo paramItem = reqVo.getParamItem(); if (paramItem != null) { GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie Integer parseStrategy = paramItem.getParseStrategy(); if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy); } itemEntity.setParseStrategy(paramItem.getParseStrategy()); // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填 if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { // 参数名称 String fieldName = paramItem.getFieldName(); if (StringUtil.isBlank(fieldName)) { return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); } String pattern = paramItem.getPattern(); // 如果匹配串不为空,验证匹配模式 if (StringUtil.isNotEmpty(pattern)) { itemEntity.setPattern(pattern); Integer matchStrategy = paramItem.getMatchStrategy(); if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } itemEntity.setMatchStrategy(matchStrategy); } } else { entity.setParamItem(null); } // 阈值类型 0-线程数 1-QPS Integer grade = reqVo.getGrade(); if (grade == null) { return Result.ofFail(-1, "grade can't be null"); } if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) { return Result.ofFail(-1, "invalid grade: " + grade); } entity.setGrade(grade); // QPS阈值 Double count = reqVo.getCount(); if (count == null) { return Result.ofFail(-1, "count can't be null"); } if (count < 0) { return Result.ofFail(-1, "count should be at lease zero"); } entity.setCount(count); // 间隔 Long interval = reqVo.getInterval(); if (interval == null) { return Result.ofFail(-1, "interval can't be null"); } if (interval <= 0) { return Result.ofFail(-1, "interval should be greater than zero"); } entity.setInterval(interval); // 间隔单位 Integer intervalUnit = reqVo.getIntervalUnit(); if (intervalUnit == null) { return Result.ofFail(-1, "intervalUnit can't be null"); } if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) { return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit); } entity.setIntervalUnit(intervalUnit); // 流控方式 0-快速失败 2-匀速排队 Integer controlBehavior = reqVo.getControlBehavior(); if (controlBehavior == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) { return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior); } entity.setControlBehavior(controlBehavior); if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) { // 0-快速失败, 则Burst size必填 Integer burst = reqVo.getBurst(); if (burst == null) { return Result.ofFail(-1, "burst can't be null"); } if (burst < 0) { return Result.ofFail(-1, "invalid burst: " + burst); } entity.setBurst(burst); } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) { // 2-匀速排队, 则超时时间必填 Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs(); if (maxQueueingTimeoutMs == null) { return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null"); } if (maxQueueingTimeoutMs < 0) { return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs); } entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs); } Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("update gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(app, entity.getIp(), entity.getPort())) { logger.warn("publish gateway flow rules fail after update"); } return Result.ofSuccess(entity); } @PostMapping("/delete.json") @AuthAction(AuthService.PrivilegeType.DELETE_RULE) public Result deleteFlowRule(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } GatewayFlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("delete gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.warn("publish gateway flow rules fail after delete"); } return Result.ofSuccess(id); } private boolean publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/v2/FlowControllerV2.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.v2; import java.util.Date; import java.util.List; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.dashboard.domain.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Flow rule controller (v2). * * @author Eric Zhao * @since 1.4.0 */ @RestController @RequestMapping(value = "/v2/flow") public class FlowControllerV2 { private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class); @Autowired private InMemoryRuleRepositoryAdapter repository; @Autowired @Qualifier("flowRuleDefaultProvider") private DynamicRuleProvider> ruleProvider; @Autowired @Qualifier("flowRuleDefaultPublisher") private DynamicRulePublisher> rulePublisher; @GetMapping("/rules") @AuthAction(PrivilegeType.READ_RULE) public Result> apiQueryMachineRules(@RequestParam String app) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } try { List rules = ruleProvider.getRules(app); if (rules != null && !rules.isEmpty()) { for (FlowRuleEntity entity : rules) { entity.setApp(app); if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) { entity.setId(entity.getClusterConfig().getFlowId()); } } } rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("Error when querying flow rules", throwable); return Result.ofThrowable(-1, throwable); } } private Result checkEntityInternal(FlowRuleEntity entity) { if (entity == null) { return Result.ofFail(-1, "invalid body"); } if (StringUtil.isBlank(entity.getApp())) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isBlank(entity.getLimitApp())) { return Result.ofFail(-1, "limitApp can't be null or empty"); } if (StringUtil.isBlank(entity.getResource())) { return Result.ofFail(-1, "resource can't be null or empty"); } if (entity.getGrade() == null) { return Result.ofFail(-1, "grade can't be null"); } if (entity.getGrade() != 0 && entity.getGrade() != 1) { return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got"); } if (entity.getCount() == null || entity.getCount() < 0) { return Result.ofFail(-1, "count should be at lease zero"); } if (entity.getStrategy() == null) { return Result.ofFail(-1, "strategy can't be null"); } if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) { return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0"); } if (entity.getControlBehavior() == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } int controlBehavior = entity.getControlBehavior(); if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) { return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1"); } if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) { return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2"); } if (entity.isClusterMode() && entity.getClusterConfig() == null) { return Result.ofFail(-1, "cluster config should be valid"); } return null; } @PostMapping("/rule") @AuthAction(value = AuthService.PrivilegeType.WRITE_RULE) public Result apiAddFlowRule(@RequestBody FlowRuleEntity entity) { Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setId(null); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); entity.setLimitApp(entity.getLimitApp().trim()); entity.setResource(entity.getResource().trim()); try { entity = repository.save(entity); publishRules(entity.getApp()); } catch (Throwable throwable) { logger.error("Failed to add flow rule", throwable); return Result.ofThrowable(-1, throwable); } return Result.ofSuccess(entity); } @PutMapping("/rule/{id}") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result apiUpdateFlowRule(@PathVariable("id") Long id, @RequestBody FlowRuleEntity entity) { if (id == null || id <= 0) { return Result.ofFail(-1, "Invalid id"); } FlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofFail(-1, "id " + id + " does not exist"); } if (entity == null) { return Result.ofFail(-1, "invalid body"); } entity.setApp(oldEntity.getApp()); entity.setIp(oldEntity.getIp()); entity.setPort(oldEntity.getPort()); Result checkResult = checkEntityInternal(entity); if (checkResult != null) { return checkResult; } entity.setId(id); Date date = new Date(); entity.setGmtCreate(oldEntity.getGmtCreate()); entity.setGmtModified(date); try { entity = repository.save(entity); if (entity == null) { return Result.ofFail(-1, "save entity fail"); } publishRules(oldEntity.getApp()); } catch (Throwable throwable) { logger.error("Failed to update flow rule", throwable); return Result.ofThrowable(-1, throwable); } return Result.ofSuccess(entity); } @DeleteMapping("/rule/{id}") @AuthAction(PrivilegeType.DELETE_RULE) public Result apiDeleteRule(@PathVariable("id") Long id) { if (id == null || id <= 0) { return Result.ofFail(-1, "Invalid id"); } FlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); publishRules(oldEntity.getApp()); } catch (Exception e) { return Result.ofFail(-1, e.getMessage()); } return Result.ofSuccess(id); } private void publishRules(/*@NonNull*/ String app) throws Exception { List rules = repository.findAllByApp(app); rulePublisher.publish(app, rules); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/ApplicationEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import java.util.Date; import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo; /** * @author leyou */ public class ApplicationEntity { private Long id; private Date gmtCreate; private Date gmtModified; private String app; private Integer appType; private String activeConsole; private Date lastFetch; public long getId() { return id; } public void setId(long id) { this.id = id; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Integer getAppType() { return appType; } public void setAppType(Integer appType) { this.appType = appType; } public String getActiveConsole() { return activeConsole; } public Date getLastFetch() { return lastFetch; } public void setLastFetch(Date lastFetch) { this.lastFetch = lastFetch; } public void setActiveConsole(String activeConsole) { this.activeConsole = activeConsole; } public AppInfo toAppInfo() { return new AppInfo(app, appType); } @Override public String toString() { return "ApplicationEntity{" + "id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", app='" + app + '\'' + ", activeConsole='" + activeConsole + '\'' + ", lastFetch=" + lastFetch + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MachineEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import java.util.Date; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; /** * @author leyou */ public class MachineEntity { private Long id; private Date gmtCreate; private Date gmtModified; private String app; private String ip; private String hostname; private Date timestamp; private Integer port; public long getId() { return id; } public void setId(long id) { this.id = id; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public Date getTimestamp() { return timestamp; } public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public MachineInfo toMachineInfo() { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp(app); machineInfo.setHostname(hostname); machineInfo.setIp(ip); machineInfo.setPort(port); machineInfo.setLastHeartbeat(timestamp.getTime()); machineInfo.setHeartbeatVersion(timestamp.getTime()); return machineInfo; } @Override public String toString() { return "MachineEntity{" + "id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", app='" + app + '\'' + ", ip='" + ip + '\'' + ", hostname='" + hostname + '\'' + ", timestamp=" + timestamp + ", port=" + port + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import java.util.Date; /** * @author leyou */ public class MetricEntity { private Long id; private Date gmtCreate; private Date gmtModified; private String app; /** * 监控信息的时间戳 */ private Date timestamp; private String resource; private Long passQps; private Long successQps; private Long blockQps; private Long exceptionQps; /** * summary rt of all success exit qps. */ private double rt; /** * 本次聚合的总条数 */ private int count; private int resourceCode; public static MetricEntity copyOf(MetricEntity oldEntity) { MetricEntity entity = new MetricEntity(); entity.setId(oldEntity.getId()); entity.setGmtCreate(oldEntity.getGmtCreate()); entity.setGmtModified(oldEntity.getGmtModified()); entity.setApp(oldEntity.getApp()); entity.setTimestamp(oldEntity.getTimestamp()); entity.setResource(oldEntity.getResource()); entity.setPassQps(oldEntity.getPassQps()); entity.setBlockQps(oldEntity.getBlockQps()); entity.setSuccessQps(oldEntity.getSuccessQps()); entity.setExceptionQps(oldEntity.getExceptionQps()); entity.setRt(oldEntity.getRt()); entity.setCount(oldEntity.getCount()); return entity; } public synchronized void addPassQps(Long passQps) { this.passQps += passQps; } public synchronized void addBlockQps(Long blockQps) { this.blockQps += blockQps; } public synchronized void addExceptionQps(Long exceptionQps) { this.exceptionQps += exceptionQps; } public synchronized void addCount(int count) { this.count += count; } public synchronized void addRtAndSuccessQps(double avgRt, Long successQps) { this.rt += avgRt * successQps; this.successQps += successQps; } /** * {@link #rt} = {@code avgRt * successQps} * * @param avgRt average rt of {@code successQps} * @param successQps */ public synchronized void setRtAndSuccessQps(double avgRt, Long successQps) { this.rt = avgRt * successQps; this.successQps = successQps; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Date getTimestamp() { return timestamp; } public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; this.resourceCode = resource.hashCode(); } public Long getPassQps() { return passQps; } public void setPassQps(Long passQps) { this.passQps = passQps; } public Long getBlockQps() { return blockQps; } public void setBlockQps(Long blockQps) { this.blockQps = blockQps; } public Long getExceptionQps() { return exceptionQps; } public void setExceptionQps(Long exceptionQps) { this.exceptionQps = exceptionQps; } public double getRt() { return rt; } public void setRt(double rt) { this.rt = rt; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public int getResourceCode() { return resourceCode; } public Long getSuccessQps() { return successQps; } public void setSuccessQps(Long successQps) { this.successQps = successQps; } @Override public String toString() { return "MetricEntity{" + "id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", app='" + app + '\'' + ", timestamp=" + timestamp + ", resource='" + resource + '\'' + ", passQps=" + passQps + ", blockQps=" + blockQps + ", successQps=" + successQps + ", exceptionQps=" + exceptionQps + ", rt=" + rt + ", count=" + count + ", resourceCode=" + resourceCode + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricPositionEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import java.util.Date; /** * @author leyou */ public class MetricPositionEntity { private long id; private Date gmtCreate; private Date gmtModified; private String app; private String ip; /** * Sentinel在该应用上使用的端口 */ private int port; /** * 机器名,冗余字段 */ private String hostname; /** * 上一次拉取的最晚时间戳 */ private Date lastFetch; public long getId() { return id; } public void setId(long id) { this.id = id; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } 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 String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public Date getLastFetch() { return lastFetch; } public void setLastFetch(Date lastFetch) { this.lastFetch = lastFetch; } @Override public String toString() { return "MetricPositionEntity{" + "id=" + id + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", app='" + app + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", hostname='" + hostname + '\'' + ", lastFetch=" + lastFetch + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; /** * @author Eric Zhao * @since 0.2.1 */ public class SentinelVersion { private int majorVersion; private int minorVersion; private int fixVersion; private String postfix; public SentinelVersion() { this(0, 0, 0); } public SentinelVersion(int major, int minor, int fix) { this(major, minor, fix, null); } public SentinelVersion(int major, int minor, int fix, String postfix) { this.majorVersion = major; this.minorVersion = minor; this.fixVersion = fix; this.postfix = postfix; } /** * 000, 000, 000 */ public int getFullVersion() { return majorVersion * 1000000 + minorVersion * 1000 + fixVersion; } public int getMajorVersion() { return majorVersion; } public SentinelVersion setMajorVersion(int majorVersion) { this.majorVersion = majorVersion; return this; } public int getMinorVersion() { return minorVersion; } public SentinelVersion setMinorVersion(int minorVersion) { this.minorVersion = minorVersion; return this; } public int getFixVersion() { return fixVersion; } public SentinelVersion setFixVersion(int fixVersion) { this.fixVersion = fixVersion; return this; } public String getPostfix() { return postfix; } public SentinelVersion setPostfix(String postfix) { this.postfix = postfix; return this; } public boolean greaterThan(SentinelVersion version) { if (version == null) { return true; } return getFullVersion() > version.getFullVersion(); } public boolean greaterOrEqual(SentinelVersion version) { if (version == null) { return true; } return getFullVersion() >= version.getFullVersion(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SentinelVersion that = (SentinelVersion)o; if (getFullVersion() != that.getFullVersion()) { return false; } return postfix != null ? postfix.equals(that.postfix) : that.postfix == null; } @Override public int hashCode() { int result = majorVersion; result = 31 * result + minorVersion; result = 31 * result + fixVersion; result = 31 * result + (postfix != null ? postfix.hashCode() : 0); return result; } @Override public String toString() { return "SentinelVersion{" + "majorVersion=" + majorVersion + ", minorVersion=" + minorVersion + ", fixVersion=" + fixVersion + ", postfix='" + postfix + '\'' + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/gateway/ApiDefinitionEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.slots.block.Rule; import java.util.Date; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; /** * Entity for {@link ApiDefinition}. * * @author cdfive * @since 1.7.0 */ public class ApiDefinitionEntity implements RuleEntity { private Long id; private String app; private String ip; private Integer port; private Date gmtCreate; private Date gmtModified; private String apiName; private Set predicateItems; public static ApiDefinitionEntity fromApiDefinition(String app, String ip, Integer port, ApiDefinition apiDefinition) { ApiDefinitionEntity entity = new ApiDefinitionEntity(); entity.setApp(app); entity.setIp(ip); entity.setPort(port); entity.setApiName(apiDefinition.getApiName()); Set predicateItems = new LinkedHashSet<>(); entity.setPredicateItems(predicateItems); Set apiPredicateItems = apiDefinition.getPredicateItems(); if (apiPredicateItems != null) { for (ApiPredicateItem apiPredicateItem : apiPredicateItems) { ApiPredicateItemEntity itemEntity = new ApiPredicateItemEntity(); predicateItems.add(itemEntity); ApiPathPredicateItem pathPredicateItem = (ApiPathPredicateItem) apiPredicateItem; itemEntity.setPattern(pathPredicateItem.getPattern()); itemEntity.setMatchStrategy(pathPredicateItem.getMatchStrategy()); } } return entity; } public ApiDefinition toApiDefinition() { ApiDefinition apiDefinition = new ApiDefinition(); apiDefinition.setApiName(apiName); Set apiPredicateItems = new LinkedHashSet<>(); apiDefinition.setPredicateItems(apiPredicateItems); if (predicateItems != null) { for (ApiPredicateItemEntity predicateItem : predicateItems) { ApiPathPredicateItem apiPredicateItem = new ApiPathPredicateItem(); apiPredicateItems.add(apiPredicateItem); apiPredicateItem.setMatchStrategy(predicateItem.getMatchStrategy()); apiPredicateItem.setPattern(predicateItem.getPattern()); } } return apiDefinition; } public ApiDefinitionEntity() { } public ApiDefinitionEntity(String apiName, Set predicateItems) { this.apiName = apiName; this.predicateItems = predicateItems; } public String getApiName() { return apiName; } public void setApiName(String apiName) { this.apiName = apiName; } public Set getPredicateItems() { return predicateItems; } public void setPredicateItems(Set predicateItems) { this.predicateItems = predicateItems; } @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } @Override public String getApp() { return app; } public void setApp(String app) { this.app = app; } @Override public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } @Override public Rule toRule() { return null; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ApiDefinitionEntity entity = (ApiDefinitionEntity) o; return Objects.equals(id, entity.id) && Objects.equals(app, entity.app) && Objects.equals(ip, entity.ip) && Objects.equals(port, entity.port) && Objects.equals(gmtCreate, entity.gmtCreate) && Objects.equals(gmtModified, entity.gmtModified) && Objects.equals(apiName, entity.apiName) && Objects.equals(predicateItems, entity.predicateItems); } @Override public int hashCode() { return Objects.hash(id, app, ip, port, gmtCreate, gmtModified, apiName, predicateItems); } @Override public String toString() { return "ApiDefinitionEntity{" + "id=" + id + ", app='" + app + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", apiName='" + apiName + '\'' + ", predicateItems=" + predicateItems + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/gateway/ApiPredicateItemEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import java.util.Objects; /** * Entity for {@link ApiPredicateItem}. * * @author cdfive * @since 1.7.0 */ public class ApiPredicateItemEntity { private String pattern; private Integer matchStrategy; public ApiPredicateItemEntity() { } public ApiPredicateItemEntity(String pattern, int matchStrategy) { this.pattern = pattern; this.matchStrategy = matchStrategy; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public Integer getMatchStrategy() { return matchStrategy; } public void setMatchStrategy(Integer matchStrategy) { this.matchStrategy = matchStrategy; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ApiPredicateItemEntity that = (ApiPredicateItemEntity) o; return Objects.equals(pattern, that.pattern) && Objects.equals(matchStrategy, that.matchStrategy); } @Override public int hashCode() { return Objects.hash(pattern, matchStrategy); } @Override public String toString() { return "ApiPredicateItemEntity{" + "pattern='" + pattern + '\'' + ", matchStrategy=" + matchStrategy + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/gateway/GatewayFlowRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.slots.block.Rule; import java.util.Date; import java.util.Objects; /** * Entity for {@link GatewayFlowRule}. * * @author cdfive * @since 1.7.0 */ public class GatewayFlowRuleEntity implements RuleEntity { /**间隔单位*/ /**0-秒*/ public static final int INTERVAL_UNIT_SECOND = 0; /**1-分*/ public static final int INTERVAL_UNIT_MINUTE = 1; /**2-时*/ public static final int INTERVAL_UNIT_HOUR = 2; /**3-天*/ public static final int INTERVAL_UNIT_DAY = 3; private Long id; private String app; private String ip; private Integer port; private Date gmtCreate; private Date gmtModified; private String resource; private Integer resourceMode; private Integer grade; private Double count; private Long interval; private Integer intervalUnit; private Integer controlBehavior; private Integer burst; private Integer maxQueueingTimeoutMs; private GatewayParamFlowItemEntity paramItem; public static Long calIntervalSec(Long interval, Integer intervalUnit) { switch (intervalUnit) { case INTERVAL_UNIT_SECOND: return interval; case INTERVAL_UNIT_MINUTE: return interval * 60; case INTERVAL_UNIT_HOUR: return interval * 60 * 60; case INTERVAL_UNIT_DAY: return interval * 60 * 60 * 24; default: break; } throw new IllegalArgumentException("Invalid intervalUnit: " + intervalUnit); } public static Object[] parseIntervalSec(Long intervalSec) { if (intervalSec % (60 * 60 * 24) == 0) { return new Object[] {intervalSec / (60 * 60 * 24), INTERVAL_UNIT_DAY}; } if (intervalSec % (60 * 60 ) == 0) { return new Object[] {intervalSec / (60 * 60), INTERVAL_UNIT_HOUR}; } if (intervalSec % 60 == 0) { return new Object[] {intervalSec / 60, INTERVAL_UNIT_MINUTE}; } return new Object[] {intervalSec, INTERVAL_UNIT_SECOND}; } public GatewayFlowRule toGatewayFlowRule() { GatewayFlowRule rule = new GatewayFlowRule(); rule.setResource(resource); rule.setResourceMode(resourceMode); rule.setGrade(grade); rule.setCount(count); rule.setIntervalSec(calIntervalSec(interval, intervalUnit)); rule.setControlBehavior(controlBehavior); if (burst != null) { rule.setBurst(burst); } if (maxQueueingTimeoutMs != null) { rule.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs); } if (paramItem != null) { GatewayParamFlowItem ruleItem = new GatewayParamFlowItem(); rule.setParamItem(ruleItem); ruleItem.setParseStrategy(paramItem.getParseStrategy()); ruleItem.setFieldName(paramItem.getFieldName()); ruleItem.setPattern(paramItem.getPattern()); if (paramItem.getMatchStrategy() != null) { ruleItem.setMatchStrategy(paramItem.getMatchStrategy()); } } return rule; } public static GatewayFlowRuleEntity fromGatewayFlowRule(String app, String ip, Integer port, GatewayFlowRule rule) { GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity(); entity.setApp(app); entity.setIp(ip); entity.setPort(port); entity.setResource(rule.getResource()); entity.setResourceMode(rule.getResourceMode()); entity.setGrade(rule.getGrade()); entity.setCount(rule.getCount()); Object[] intervalSecResult = parseIntervalSec(rule.getIntervalSec()); entity.setInterval((Long) intervalSecResult[0]); entity.setIntervalUnit((Integer) intervalSecResult[1]); entity.setControlBehavior(rule.getControlBehavior()); entity.setBurst(rule.getBurst()); entity.setMaxQueueingTimeoutMs(rule.getMaxQueueingTimeoutMs()); GatewayParamFlowItem paramItem = rule.getParamItem(); if (paramItem != null) { GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); itemEntity.setParseStrategy(paramItem.getParseStrategy()); itemEntity.setFieldName(paramItem.getFieldName()); itemEntity.setPattern(paramItem.getPattern()); itemEntity.setMatchStrategy(paramItem.getMatchStrategy()); } return entity; } @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } @Override public String getApp() { return app; } public void setApp(String app) { this.app = app; } @Override public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } @Override public Rule toRule() { return null; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } public GatewayParamFlowItemEntity getParamItem() { return paramItem; } public void setParamItem(GatewayParamFlowItemEntity paramItem) { this.paramItem = paramItem; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getResourceMode() { return resourceMode; } public void setResourceMode(Integer resourceMode) { this.resourceMode = resourceMode; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } public Long getInterval() { return interval; } public void setInterval(Long interval) { this.interval = interval; } public Integer getIntervalUnit() { return intervalUnit; } public void setIntervalUnit(Integer intervalUnit) { this.intervalUnit = intervalUnit; } public Integer getControlBehavior() { return controlBehavior; } public void setControlBehavior(Integer controlBehavior) { this.controlBehavior = controlBehavior; } public Integer getBurst() { return burst; } public void setBurst(Integer burst) { this.burst = burst; } public Integer getMaxQueueingTimeoutMs() { return maxQueueingTimeoutMs; } public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) { this.maxQueueingTimeoutMs = maxQueueingTimeoutMs; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GatewayFlowRuleEntity that = (GatewayFlowRuleEntity) o; return Objects.equals(id, that.id) && Objects.equals(app, that.app) && Objects.equals(ip, that.ip) && Objects.equals(port, that.port) && Objects.equals(gmtCreate, that.gmtCreate) && Objects.equals(gmtModified, that.gmtModified) && Objects.equals(resource, that.resource) && Objects.equals(resourceMode, that.resourceMode) && Objects.equals(grade, that.grade) && Objects.equals(count, that.count) && Objects.equals(interval, that.interval) && Objects.equals(intervalUnit, that.intervalUnit) && Objects.equals(controlBehavior, that.controlBehavior) && Objects.equals(burst, that.burst) && Objects.equals(maxQueueingTimeoutMs, that.maxQueueingTimeoutMs) && Objects.equals(paramItem, that.paramItem); } @Override public int hashCode() { return Objects.hash(id, app, ip, port, gmtCreate, gmtModified, resource, resourceMode, grade, count, interval, intervalUnit, controlBehavior, burst, maxQueueingTimeoutMs, paramItem); } @Override public String toString() { return "GatewayFlowRuleEntity{" + "id=" + id + ", app='" + app + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", gmtCreate=" + gmtCreate + ", gmtModified=" + gmtModified + ", resource='" + resource + '\'' + ", resourceMode=" + resourceMode + ", grade=" + grade + ", count=" + count + ", interval=" + interval + ", intervalUnit=" + intervalUnit + ", controlBehavior=" + controlBehavior + ", burst=" + burst + ", maxQueueingTimeoutMs=" + maxQueueingTimeoutMs + ", paramItem=" + paramItem + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/gateway/GatewayParamFlowItemEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import java.util.Objects; /** * Entity for {@link GatewayParamFlowItem}. * * @author cdfive * @since 1.7.0 */ public class GatewayParamFlowItemEntity { private Integer parseStrategy; private String fieldName; private String pattern; private Integer matchStrategy; public Integer getParseStrategy() { return parseStrategy; } public void setParseStrategy(Integer parseStrategy) { this.parseStrategy = parseStrategy; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public Integer getMatchStrategy() { return matchStrategy; } public void setMatchStrategy(Integer matchStrategy) { this.matchStrategy = matchStrategy; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GatewayParamFlowItemEntity that = (GatewayParamFlowItemEntity) o; return Objects.equals(parseStrategy, that.parseStrategy) && Objects.equals(fieldName, that.fieldName) && Objects.equals(pattern, that.pattern) && Objects.equals(matchStrategy, that.matchStrategy); } @Override public int hashCode() { return Objects.hash(parseStrategy, fieldName, pattern, matchStrategy); } @Override public String toString() { return "GatewayParamFlowItemEntity{" + "parseStrategy=" + parseStrategy + ", fieldName='" + fieldName + '\'' + ", pattern='" + pattern + '\'' + ", matchStrategy=" + matchStrategy + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AbstractRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.Date; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.Rule; /** * @author Eric Zhao * @since 0.2.1 */ public abstract class AbstractRuleEntity implements RuleEntity { protected Long id; protected String app; protected String ip; protected Integer port; protected T rule; private Date gmtCreate; private Date gmtModified; @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } @Override public String getApp() { return app; } public AbstractRuleEntity setApp(String app) { this.app = app; return this; } @Override public String getIp() { return ip; } public AbstractRuleEntity setIp(String ip) { this.ip = ip; return this; } @Override public Integer getPort() { return port; } public AbstractRuleEntity setPort(Integer port) { this.port = port; return this; } public T getRule() { return rule; } public AbstractRuleEntity setRule(T rule) { this.rule = rule; return this; } @Override public Date getGmtCreate() { return gmtCreate; } public AbstractRuleEntity setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; return this; } public Date getGmtModified() { return gmtModified; } public AbstractRuleEntity setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; return this; } @Override public T toRule() { return rule; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.fastjson.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonIgnore; /** * @author Eric Zhao * @since 0.2.1 */ public class AuthorityRuleEntity extends AbstractRuleEntity { public AuthorityRuleEntity() { } public AuthorityRuleEntity(AuthorityRule authorityRule) { AssertUtil.notNull(authorityRule, "Authority rule should not be null"); this.rule = authorityRule; } public static AuthorityRuleEntity fromAuthorityRule(String app, String ip, Integer port, AuthorityRule rule) { AuthorityRuleEntity entity = new AuthorityRuleEntity(rule); entity.setApp(app); entity.setIp(ip); entity.setPort(port); return entity; } @JsonIgnore @JSONField(serialize = false) public String getLimitApp() { return rule.getLimitApp(); } @JsonIgnore @JSONField(serialize = false) public String getResource() { return rule.getResource(); } @JsonIgnore @JSONField(serialize = false) public int getStrategy() { return rule.getStrategy(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.Date; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; /** * @author leyou */ public class DegradeRuleEntity implements RuleEntity { private Long id; private String app; private String ip; private Integer port; private String resource; private String limitApp; private Double count; private Integer timeWindow; private Integer grade; private Integer minRequestAmount; private Double slowRatioThreshold; private Integer statIntervalMs; private Date gmtCreate; private Date gmtModified; public static DegradeRuleEntity fromDegradeRule(String app, String ip, Integer port, DegradeRule rule) { DegradeRuleEntity entity = new DegradeRuleEntity(); entity.setApp(app); entity.setIp(ip); entity.setPort(port); entity.setResource(rule.getResource()); entity.setLimitApp(rule.getLimitApp()); entity.setCount(rule.getCount()); entity.setTimeWindow(rule.getTimeWindow()); entity.setGrade(rule.getGrade()); entity.setMinRequestAmount(rule.getMinRequestAmount()); entity.setSlowRatioThreshold(rule.getSlowRatioThreshold()); entity.setStatIntervalMs(rule.getStatIntervalMs()); return entity; } @Override public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } @Override public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public String getLimitApp() { return limitApp; } public void setLimitApp(String limitApp) { this.limitApp = limitApp; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } public Integer getTimeWindow() { return timeWindow; } public void setTimeWindow(Integer timeWindow) { this.timeWindow = timeWindow; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public Integer getMinRequestAmount() { return minRequestAmount; } public DegradeRuleEntity setMinRequestAmount(Integer minRequestAmount) { this.minRequestAmount = minRequestAmount; return this; } public Double getSlowRatioThreshold() { return slowRatioThreshold; } public DegradeRuleEntity setSlowRatioThreshold(Double slowRatioThreshold) { this.slowRatioThreshold = slowRatioThreshold; return this; } public Integer getStatIntervalMs() { return statIntervalMs; } public DegradeRuleEntity setStatIntervalMs(Integer statIntervalMs) { this.statIntervalMs = statIntervalMs; return this; } @Override public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } @Override public DegradeRule toRule() { DegradeRule rule = new DegradeRule(); rule.setResource(resource); rule.setLimitApp(limitApp); rule.setCount(count); rule.setTimeWindow(timeWindow); rule.setGrade(grade); if (minRequestAmount != null) { rule.setMinRequestAmount(minRequestAmount); } if (slowRatioThreshold != null) { rule.setSlowRatioThreshold(slowRatioThreshold); } if (statIntervalMs != null) { rule.setStatIntervalMs(statIntervalMs); } return rule; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.Date; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; /** * @author leyou */ public class FlowRuleEntity implements RuleEntity { private Long id; private String app; private String ip; private Integer port; private String limitApp; private String resource; /** * 0为线程数;1为qps */ private Integer grade; private Double count; /** * 0为直接限流;1为关联限流;2为链路限流 ***/ private Integer strategy; private String refResource; /** * 0. default, 1. warm up, 2. rate limiter */ private Integer controlBehavior; private Integer warmUpPeriodSec; /** * max queueing time in rate limiter behavior */ private Integer maxQueueingTimeMs; private boolean clusterMode; /** * Flow rule config for cluster mode. */ private ClusterFlowConfig clusterConfig; private Date gmtCreate; private Date gmtModified; public static FlowRuleEntity fromFlowRule(String app, String ip, Integer port, FlowRule rule) { FlowRuleEntity entity = new FlowRuleEntity(); entity.setApp(app); entity.setIp(ip); entity.setPort(port); entity.setLimitApp(rule.getLimitApp()); entity.setResource(rule.getResource()); entity.setGrade(rule.getGrade()); entity.setCount(rule.getCount()); entity.setStrategy(rule.getStrategy()); entity.setRefResource(rule.getRefResource()); entity.setControlBehavior(rule.getControlBehavior()); entity.setWarmUpPeriodSec(rule.getWarmUpPeriodSec()); entity.setMaxQueueingTimeMs(rule.getMaxQueueingTimeMs()); entity.setClusterMode(rule.isClusterMode()); entity.setClusterConfig(rule.getClusterConfig()); return entity; } @Override public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public String getApp() { return app; } public void setApp(String app) { this.app = app; } @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } public String getLimitApp() { return limitApp; } public void setLimitApp(String limitApp) { this.limitApp = limitApp; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } public Integer getStrategy() { return strategy; } public void setStrategy(Integer strategy) { this.strategy = strategy; } public String getRefResource() { return refResource; } public void setRefResource(String refResource) { this.refResource = refResource; } public Integer getControlBehavior() { return controlBehavior; } public void setControlBehavior(Integer controlBehavior) { this.controlBehavior = controlBehavior; } public Integer getWarmUpPeriodSec() { return warmUpPeriodSec; } public void setWarmUpPeriodSec(Integer warmUpPeriodSec) { this.warmUpPeriodSec = warmUpPeriodSec; } public Integer getMaxQueueingTimeMs() { return maxQueueingTimeMs; } public void setMaxQueueingTimeMs(Integer maxQueueingTimeMs) { this.maxQueueingTimeMs = maxQueueingTimeMs; } public boolean isClusterMode() { return clusterMode; } public FlowRuleEntity setClusterMode(boolean clusterMode) { this.clusterMode = clusterMode; return this; } public ClusterFlowConfig getClusterConfig() { return clusterConfig; } public FlowRuleEntity setClusterConfig(ClusterFlowConfig clusterConfig) { this.clusterConfig = clusterConfig; return this; } @Override public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } @Override public FlowRule toRule() { FlowRule flowRule = new FlowRule(); flowRule.setCount(this.count); flowRule.setGrade(this.grade); flowRule.setResource(this.resource); flowRule.setLimitApp(this.limitApp); flowRule.setRefResource(this.refResource); flowRule.setStrategy(this.strategy); if (this.controlBehavior != null) { flowRule.setControlBehavior(controlBehavior); } if (this.warmUpPeriodSec != null) { flowRule.setWarmUpPeriodSec(warmUpPeriodSec); } if (this.maxQueueingTimeMs != null) { flowRule.setMaxQueueingTimeMs(maxQueueingTimeMs); } flowRule.setClusterMode(clusterMode); flowRule.setClusterConfig(clusterConfig); return flowRule; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.fastjson.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.List; /** * @author Eric Zhao * @since 0.2.1 */ public class ParamFlowRuleEntity extends AbstractRuleEntity { public ParamFlowRuleEntity() { } public ParamFlowRuleEntity(ParamFlowRule rule) { AssertUtil.notNull(rule, "Authority rule should not be null"); this.rule = rule; } public static ParamFlowRuleEntity fromParamFlowRule(String app, String ip, Integer port, ParamFlowRule rule) { ParamFlowRuleEntity entity = new ParamFlowRuleEntity(rule); entity.setApp(app); entity.setIp(ip); entity.setPort(port); return entity; } @JsonIgnore @JSONField(serialize = false) public String getLimitApp() { return rule.getLimitApp(); } @JsonIgnore @JSONField(serialize = false) public String getResource() { return rule.getResource(); } @JsonIgnore @JSONField(serialize = false) public int getGrade() { return rule.getGrade(); } @JsonIgnore @JSONField(serialize = false) public Integer getParamIdx() { return rule.getParamIdx(); } @JsonIgnore @JSONField(serialize = false) public double getCount() { return rule.getCount(); } @JsonIgnore @JSONField(serialize = false) public List getParamFlowItemList() { return rule.getParamFlowItemList(); } @JsonIgnore @JSONField(serialize = false) public int getControlBehavior() { return rule.getControlBehavior(); } @JsonIgnore @JSONField(serialize = false) public int getMaxQueueingTimeMs() { return rule.getMaxQueueingTimeMs(); } @JsonIgnore @JSONField(serialize = false) public int getBurstCount() { return rule.getBurstCount(); } @JsonIgnore @JSONField(serialize = false) public long getDurationInSec() { return rule.getDurationInSec(); } @JsonIgnore @JSONField(serialize = false) public boolean isClusterMode() { return rule.isClusterMode(); } @JsonIgnore @JSONField(serialize = false) public ParamFlowClusterConfig getClusterConfig() { return rule.getClusterConfig(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.Date; import com.alibaba.csp.sentinel.slots.block.Rule; /** * @author leyou */ public interface RuleEntity { Long getId(); void setId(Long id); String getApp(); String getIp(); Integer getPort(); Date getGmtCreate(); Rule toRule(); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import java.util.Date; /** * @author leyou */ public class SystemRuleEntity implements RuleEntity { private Long id; private String app; private String ip; private Integer port; private Double highestSystemLoad; private Long avgRt; private Long maxThread; private Double qps; private Double highestCpuUsage; private Date gmtCreate; private Date gmtModified; public static SystemRuleEntity fromSystemRule(String app, String ip, Integer port, SystemRule rule) { SystemRuleEntity entity = new SystemRuleEntity(); entity.setApp(app); entity.setIp(ip); entity.setPort(port); entity.setHighestSystemLoad(rule.getHighestSystemLoad()); entity.setHighestCpuUsage(rule.getHighestCpuUsage()); entity.setAvgRt(rule.getAvgRt()); entity.setMaxThread(rule.getMaxThread()); entity.setQps(rule.getQps()); return entity; } @Override public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public Long getId() { return id; } @Override public void setId(Long id) { this.id = id; } @Override public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Double getHighestSystemLoad() { return highestSystemLoad; } public void setHighestSystemLoad(Double highestSystemLoad) { this.highestSystemLoad = highestSystemLoad; } public Long getAvgRt() { return avgRt; } public void setAvgRt(Long avgRt) { this.avgRt = avgRt; } public Long getMaxThread() { return maxThread; } public void setMaxThread(Long maxThread) { this.maxThread = maxThread; } public Double getQps() { return qps; } public void setQps(Double qps) { this.qps = qps; } public Double getHighestCpuUsage() { return highestCpuUsage; } public void setHighestCpuUsage(Double highestCpuUsage) { this.highestCpuUsage = highestCpuUsage; } @Override public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public Date getGmtModified() { return gmtModified; } public void setGmtModified(Date gmtModified) { this.gmtModified = gmtModified; } @Override public SystemRule toRule() { SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(highestSystemLoad); rule.setAvgRt(avgRt); rule.setMaxThread(maxThread); rule.setQps(qps); rule.setHighestCpuUsage(highestCpuUsage); return rule; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/discovery/AppInfo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.Comparator; import java.util.HashSet; import java.util.Optional; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; public class AppInfo { private String app = ""; private Integer appType = 0; private Set machines = ConcurrentHashMap.newKeySet(); public AppInfo() {} public AppInfo(String app) { this.app = app; } public AppInfo(String app, Integer appType) { this.app = app; this.appType = appType; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Integer getAppType() { return appType; } public void setAppType(Integer appType) { this.appType = appType; } /** * Get the current machines. * * @return a new copy of the current machines. */ public Set getMachines() { return new HashSet<>(machines); } @Override public String toString() { return "AppInfo{" + "app='" + app + ", machines=" + machines + '}'; } public boolean addMachine(MachineInfo machineInfo) { machines.remove(machineInfo); return machines.add(machineInfo); } public synchronized boolean removeMachine(String ip, int port) { Iterator it = machines.iterator(); while (it.hasNext()) { MachineInfo machine = it.next(); if (machine.getIp().equals(ip) && machine.getPort() == port) { it.remove(); return true; } } return false; } public Optional getMachine(String ip, int port) { return machines.stream() .filter(e -> e.getIp().equals(ip) && e.getPort().equals(port)) .findFirst(); } public Optional getMachine(String ip) { return machines.stream() .filter(e -> e.getIp().equals(ip)) .findFirst(); } private boolean heartbeatJudge(final int threshold) { if (machines.size() == 0) { return false; } if (threshold > 0) { long healthyCount = machines.stream() .filter(MachineInfo::isHealthy) .count(); if (healthyCount == 0) { // No healthy machines. return machines.stream() .max(Comparator.comparingLong(MachineInfo::getLastHeartbeat)) .map(e -> System.currentTimeMillis() - e.getLastHeartbeat() < threshold) .orElse(false); } } return true; } /** * Check whether current application has no healthy machines and should not be displayed. * * @return true if the application should be displayed in the sidebar, otherwise false */ public boolean isShown() { return heartbeatJudge(DashboardConfig.getHideAppNoMachineMillis()); } /** * Check whether current application has no healthy machines and should be removed. * * @return true if the application is dead and should be removed, otherwise false */ public boolean isDead() { return !heartbeatJudge(DashboardConfig.getRemoveAppNoMachineMillis()); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/discovery/AppManagement.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.PostConstruct; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; @Component public class AppManagement implements MachineDiscovery { @Autowired private ApplicationContext context; private MachineDiscovery machineDiscovery; @PostConstruct public void init() { machineDiscovery = context.getBean(SimpleMachineDiscovery.class); } @Override public Set getBriefApps() { return machineDiscovery.getBriefApps(); } @Override public long addMachine(MachineInfo machineInfo) { return machineDiscovery.addMachine(machineInfo); } @Override public boolean removeMachine(String app, String ip, int port) { return machineDiscovery.removeMachine(app, ip, port); } @Override public List getAppNames() { return machineDiscovery.getAppNames(); } @Override public AppInfo getDetailApp(String app) { return machineDiscovery.getDetailApp(app); } @Override public void removeApp(String app) { machineDiscovery.removeApp(app); } public boolean isValidMachineOfApp(String app, String ip) { if (StringUtil.isEmpty(app)) { return false; } return Optional.ofNullable(getDetailApp(app)) .flatMap(a -> a.getMachine(ip)) .isPresent(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/discovery/MachineDiscovery.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.List; import java.util.Set; public interface MachineDiscovery { String UNKNOWN_APP_NAME = "CLUSTER_NOT_STARTED"; List getAppNames(); Set getBriefApps(); AppInfo getDetailApp(String app); /** * Remove the given app from the application registry. * * @param app application name * @since 1.5.0 */ void removeApp(String app); long addMachine(MachineInfo machineInfo); /** * Remove the given machine instance from the application registry. * * @param app the application name of the machine * @param ip machine IP * @param port machine port * @return true if removed, otherwise false * @since 1.5.0 */ boolean removeMachine(String app, String ip, int port); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/discovery/MachineInfo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.Objects; import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; import com.alibaba.csp.sentinel.util.StringUtil; public class MachineInfo implements Comparable { private String app = ""; private Integer appType = 0; private String hostname = ""; private String ip = ""; private Integer port = -1; private long lastHeartbeat; private long heartbeatVersion; /** * Indicates the version of Sentinel client (since 0.2.0). */ private String version; public static MachineInfo of(String app, String ip, Integer port) { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp(app); machineInfo.setIp(ip); machineInfo.setPort(port); return machineInfo; } public String toHostPort() { return ip + ":" + port; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Integer getAppType() { return appType; } public void setAppType(Integer appType) { this.appType = appType; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public long getHeartbeatVersion() { return heartbeatVersion; } public void setHeartbeatVersion(long heartbeatVersion) { this.heartbeatVersion = heartbeatVersion; } public String getVersion() { return version; } public MachineInfo setVersion(String version) { this.version = version; return this; } public boolean isHealthy() { long delta = System.currentTimeMillis() - lastHeartbeat; return delta < DashboardConfig.getUnhealthyMachineMillis(); } /** * whether dead should be removed * * @return */ public boolean isDead() { if (DashboardConfig.getAutoRemoveMachineMillis() > 0) { long delta = System.currentTimeMillis() - lastHeartbeat; return delta > DashboardConfig.getAutoRemoveMachineMillis(); } return false; } public long getLastHeartbeat() { return lastHeartbeat; } public void setLastHeartbeat(long lastHeartbeat) { this.lastHeartbeat = lastHeartbeat; } @Override public int compareTo(MachineInfo o) { if (this == o) { return 0; } if (!port.equals(o.getPort())) { return port.compareTo(o.getPort()); } if (!StringUtil.equals(app, o.getApp())) { return app.compareToIgnoreCase(o.getApp()); } return ip.compareToIgnoreCase(o.getIp()); } @Override public String toString() { return new StringBuilder("MachineInfo {") .append("app='").append(app).append('\'') .append(",appType='").append(appType).append('\'') .append(", hostname='").append(hostname).append('\'') .append(", ip='").append(ip).append('\'') .append(", port=").append(port) .append(", heartbeatVersion=").append(heartbeatVersion) .append(", lastHeartbeat=").append(lastHeartbeat) .append(", version='").append(version).append('\'') .append(", healthy=").append(isHealthy()) .append('}').toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof MachineInfo)) { return false; } MachineInfo that = (MachineInfo)o; return Objects.equals(app, that.app) && Objects.equals(ip, that.ip) && Objects.equals(port, that.port); } @Override public int hashCode() { return Objects.hash(app, ip, port); } /** * Information for log * * @return */ public String toLogString() { return app + "|" + ip + "|" + port + "|" + version; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/discovery/SimpleMachineDiscovery.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.alibaba.csp.sentinel.util.AssertUtil; import org.springframework.stereotype.Component; /** * @author leyou */ @Component public class SimpleMachineDiscovery implements MachineDiscovery { private final ConcurrentMap apps = new ConcurrentHashMap<>(); @Override public long addMachine(MachineInfo machineInfo) { AssertUtil.notNull(machineInfo, "machineInfo cannot be null"); AppInfo appInfo = apps.computeIfAbsent(machineInfo.getApp(), o -> new AppInfo(machineInfo.getApp(), machineInfo.getAppType())); appInfo.addMachine(machineInfo); return 1; } @Override public boolean removeMachine(String app, String ip, int port) { AssertUtil.assertNotBlank(app, "app name cannot be blank"); AppInfo appInfo = apps.get(app); if (appInfo != null) { return appInfo.removeMachine(ip, port); } return false; } @Override public List getAppNames() { return new ArrayList<>(apps.keySet()); } @Override public AppInfo getDetailApp(String app) { AssertUtil.assertNotBlank(app, "app name cannot be blank"); return apps.get(app); } @Override public Set getBriefApps() { return new HashSet<>(apps.values()); } @Override public void removeApp(String app) { AssertUtil.assertNotBlank(app, "app name cannot be blank"); apps.remove(app); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/ResourceTreeNode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.csp.sentinel.command.vo.NodeVo; /** * @author leyou */ public class ResourceTreeNode { private String id; private String parentId; private String resource; private Integer threadNum; private Long passQps; private Long blockQps; private Long totalQps; private Long averageRt; private Long successQps; private Long exceptionQps; private Long oneMinutePass; private Long oneMinuteBlock; private Long oneMinuteException; private Long oneMinuteTotal; private boolean visible = true; private List children = new ArrayList<>(); public static ResourceTreeNode fromNodeVoList(List nodeVos) { if (nodeVos == null || nodeVos.isEmpty()) { return null; } ResourceTreeNode root = null; Map map = new HashMap<>(); for (NodeVo vo : nodeVos) { ResourceTreeNode node = fromNodeVo(vo); map.put(node.id, node); // real root if (node.parentId == null || node.parentId.isEmpty()) { root = node; } else if (map.containsKey(node.parentId)) { map.get(node.parentId).children.add(node); } else { // impossible } } return root; } public static ResourceTreeNode fromNodeVo(NodeVo vo) { ResourceTreeNode node = new ResourceTreeNode(); node.id = vo.getId(); node.parentId = vo.getParentId(); node.resource = vo.getResource(); node.threadNum = vo.getThreadNum(); node.passQps = vo.getPassQps(); node.blockQps = vo.getBlockQps(); node.totalQps = vo.getTotalQps(); node.averageRt = vo.getAverageRt(); node.successQps = vo.getSuccessQps(); node.exceptionQps = vo.getExceptionQps(); node.oneMinutePass = vo.getOneMinutePass(); node.oneMinuteBlock = vo.getOneMinuteBlock(); node.oneMinuteException = vo.getOneMinuteException(); node.oneMinuteTotal = vo.getOneMinuteTotal(); return node; } public void searchIgnoreCase(String searchKey) { search(this, searchKey); } /** * This node is visible only when searchKey matches this.resource or at least * one of this's children is visible */ private boolean search(ResourceTreeNode node, String searchKey) { // empty matches all if (searchKey == null || searchKey.isEmpty() || node.resource.toLowerCase().contains(searchKey.toLowerCase())) { node.visible = true; } else { node.visible = false; } boolean found = false; for (ResourceTreeNode c : node.children) { found |= search(c, searchKey); } node.visible |= found; return node.visible; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getThreadNum() { return threadNum; } public void setThreadNum(Integer threadNum) { this.threadNum = threadNum; } public Long getPassQps() { return passQps; } public void setPassQps(Long passQps) { this.passQps = passQps; } public Long getBlockQps() { return blockQps; } public void setBlockQps(Long blockQps) { this.blockQps = blockQps; } public Long getTotalQps() { return totalQps; } public void setTotalQps(Long totalQps) { this.totalQps = totalQps; } public Long getAverageRt() { return averageRt; } public void setAverageRt(Long averageRt) { this.averageRt = averageRt; } public Long getSuccessQps() { return successQps; } public void setSuccessQps(Long successQps) { this.successQps = successQps; } public Long getExceptionQps() { return exceptionQps; } public void setExceptionQps(Long exceptionQps) { this.exceptionQps = exceptionQps; } public Long getOneMinutePass() { return oneMinutePass; } public void setOneMinutePass(Long oneMinutePass) { this.oneMinutePass = oneMinutePass; } public Long getOneMinuteBlock() { return oneMinuteBlock; } public void setOneMinuteBlock(Long oneMinuteBlock) { this.oneMinuteBlock = oneMinuteBlock; } public Long getOneMinuteException() { return oneMinuteException; } public void setOneMinuteException(Long oneMinuteException) { this.oneMinuteException = oneMinuteException; } public Long getOneMinuteTotal() { return oneMinuteTotal; } public void setOneMinuteTotal(Long oneMinuteTotal) { this.oneMinuteTotal = oneMinuteTotal; } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { this.visible = visible; } public List getChildren() { return children; } public void setChildren(List children) { this.children = children; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/Result.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain; /** * @author leyou * @author Eric Zhao */ public class Result { private boolean success; private int code; private String msg; private R data; public static Result ofSuccess(R data) { return new Result() .setSuccess(true) .setMsg("success") .setData(data); } public static Result ofSuccessMsg(String msg) { return new Result() .setSuccess(true) .setMsg(msg); } public static Result ofFail(int code, String msg) { Result result = new Result<>(); result.setSuccess(false); result.setCode(code); result.setMsg(msg); return result; } public static Result ofThrowable(int code, Throwable throwable) { Result result = new Result<>(); result.setSuccess(false); result.setCode(code); result.setMsg(throwable.getClass().getName() + ", " + throwable.getMessage()); return result; } public boolean isSuccess() { return success; } public Result setSuccess(boolean success) { this.success = success; return this; } public int getCode() { return code; } public Result setCode(int code) { this.code = code; return this; } public String getMsg() { return msg; } public Result setMsg(String msg) { this.msg = msg; return this; } public R getData() { return data; } public Result setData(R data) { this.data = data; return this; } @Override public String toString() { return "Result{" + "success=" + success + ", code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterAppAssignResultVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; import java.util.Set; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterAppAssignResultVO { private Set failedServerSet; private Set failedClientSet; private Integer totalCount; public Set getFailedServerSet() { return failedServerSet; } public ClusterAppAssignResultVO setFailedServerSet(Set failedServerSet) { this.failedServerSet = failedServerSet; return this; } public Set getFailedClientSet() { return failedClientSet; } public ClusterAppAssignResultVO setFailedClientSet(Set failedClientSet) { this.failedClientSet = failedClientSet; return this; } public Integer getTotalCount() { return totalCount; } public ClusterAppAssignResultVO setTotalCount(Integer totalCount) { this.totalCount = totalCount; return this; } @Override public String toString() { return "ClusterAppAssignResultVO{" + "failedServerSet=" + failedServerSet + ", failedClientSet=" + failedClientSet + ", totalCount=" + totalCount + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterAppFullAssignRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterAppAssignMap; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterAppFullAssignRequest { private List clusterMap; private Set remainingList; public List getClusterMap() { return clusterMap; } public ClusterAppFullAssignRequest setClusterMap( List clusterMap) { this.clusterMap = clusterMap; return this; } public Set getRemainingList() { return remainingList; } public ClusterAppFullAssignRequest setRemainingList(Set remainingList) { this.remainingList = remainingList; return this; } @Override public String toString() { return "ClusterAppFullAssignRequest{" + "clusterMap=" + clusterMap + ", remainingList=" + remainingList + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterAppSingleServerAssignRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterAppAssignMap; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterAppSingleServerAssignRequest { private ClusterAppAssignMap clusterMap; private Set remainingList; public ClusterAppAssignMap getClusterMap() { return clusterMap; } public ClusterAppSingleServerAssignRequest setClusterMap(ClusterAppAssignMap clusterMap) { this.clusterMap = clusterMap; return this; } public Set getRemainingList() { return remainingList; } public ClusterAppSingleServerAssignRequest setRemainingList(Set remainingList) { this.remainingList = remainingList; return this; } @Override public String toString() { return "ClusterAppSingleServerAssignRequest{" + "clusterMap=" + clusterMap + ", remainingList=" + remainingList + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterClientInfoVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterClientInfoVO { private String serverHost; private Integer serverPort; private Integer clientState; private Integer requestTimeout; public String getServerHost() { return serverHost; } public ClusterClientInfoVO setServerHost(String serverHost) { this.serverHost = serverHost; return this; } public Integer getServerPort() { return serverPort; } public ClusterClientInfoVO setServerPort(Integer serverPort) { this.serverPort = serverPort; return this; } public Integer getClientState() { return clientState; } public ClusterClientInfoVO setClientState(Integer clientState) { this.clientState = clientState; return this; } public Integer getRequestTimeout() { return requestTimeout; } public ClusterClientInfoVO setRequestTimeout(Integer requestTimeout) { this.requestTimeout = requestTimeout; return this; } @Override public String toString() { return "ClusterClientInfoVO{" + "serverHost='" + serverHost + '\'' + ", serverPort=" + serverPort + ", clientState=" + clientState + ", requestTimeout=" + requestTimeout + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterGroupEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; import java.util.HashSet; import java.util.Set; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterGroupEntity { private String machineId; private String ip; private Integer port; private Set clientSet = new HashSet<>(); private Boolean belongToApp; public String getMachineId() { return machineId; } public ClusterGroupEntity setMachineId(String machineId) { this.machineId = machineId; return this; } public String getIp() { return ip; } public ClusterGroupEntity setIp(String ip) { this.ip = ip; return this; } public Integer getPort() { return port; } public ClusterGroupEntity setPort(Integer port) { this.port = port; return this; } public Set getClientSet() { return clientSet; } public ClusterGroupEntity setClientSet(Set clientSet) { this.clientSet = clientSet; return this; } public Boolean getBelongToApp() { return belongToApp; } public ClusterGroupEntity setBelongToApp(Boolean belongToApp) { this.belongToApp = belongToApp; return this; } @Override public String toString() { return "ClusterGroupEntity{" + "machineId='" + machineId + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", clientSet=" + clientSet + ", belongToApp=" + belongToApp + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ClusterStateSingleVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterStateSingleVO { private String address; private Integer mode; private String target; public String getAddress() { return address; } public ClusterStateSingleVO setAddress(String address) { this.address = address; return this; } public Integer getMode() { return mode; } public ClusterStateSingleVO setMode(Integer mode) { this.mode = mode; return this; } public String getTarget() { return target; } public ClusterStateSingleVO setTarget(String target) { this.target = target; return this; } @Override public String toString() { return "ClusterStateSingleVO{" + "address='" + address + '\'' + ", mode=" + mode + ", target='" + target + '\'' + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; /** * @author Eric Zhao * @since 1.4.0 */ public class ConnectionDescriptorVO { private String address; private String host; public String getAddress() { return address; } public ConnectionDescriptorVO setAddress(String address) { this.address = address; return this; } public String getHost() { return host; } public ConnectionDescriptorVO setHost(String host) { this.host = host; return this; } @Override public String toString() { return "ConnectionDescriptorVO{" + "address='" + address + '\'' + ", host='" + host + '\'' + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ public class ConnectionGroupVO { private String namespace; private List connectionSet; private Integer connectedCount; public String getNamespace() { return namespace; } public ConnectionGroupVO setNamespace(String namespace) { this.namespace = namespace; return this; } public List getConnectionSet() { return connectionSet; } public ConnectionGroupVO setConnectionSet( List connectionSet) { this.connectionSet = connectionSet; return this; } public Integer getConnectedCount() { return connectedCount; } public ConnectionGroupVO setConnectedCount(Integer connectedCount) { this.connectedCount = connectedCount; return this; } @Override public String toString() { return "ConnectionGroupVO{" + "namespace='" + namespace + '\'' + ", connectionSet=" + connectionSet + ", connectedCount=" + connectedCount + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.config; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterClientConfig { private String serverHost; private Integer serverPort; private Integer requestTimeout; private Integer connectTimeout; public String getServerHost() { return serverHost; } public ClusterClientConfig setServerHost(String serverHost) { this.serverHost = serverHost; return this; } public Integer getServerPort() { return serverPort; } public ClusterClientConfig setServerPort(Integer serverPort) { this.serverPort = serverPort; return this; } public Integer getRequestTimeout() { return requestTimeout; } public ClusterClientConfig setRequestTimeout(Integer requestTimeout) { this.requestTimeout = requestTimeout; return this; } public Integer getConnectTimeout() { return connectTimeout; } public ClusterClientConfig setConnectTimeout(Integer connectTimeout) { this.connectTimeout = connectTimeout; return this; } @Override public String toString() { return "ClusterClientConfig{" + "serverHost='" + serverHost + '\'' + ", serverPort=" + serverPort + ", requestTimeout=" + requestTimeout + ", connectTimeout=" + connectTimeout + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.config; /** * @author Eric Zhao * @since 1.4.0 */ public class ServerFlowConfig { public static final double DEFAULT_EXCEED_COUNT = 1.0d; public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d; public static final int DEFAULT_INTERVAL_MS = 1000; public static final int DEFAULT_SAMPLE_COUNT= 10; public static final double DEFAULT_MAX_ALLOWED_QPS= 30000; private final String namespace; private Double exceedCount = DEFAULT_EXCEED_COUNT; private Double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO; private Integer intervalMs = DEFAULT_INTERVAL_MS; private Integer sampleCount = DEFAULT_SAMPLE_COUNT; private Double maxAllowedQps = DEFAULT_MAX_ALLOWED_QPS; public ServerFlowConfig() { this("default"); } public ServerFlowConfig(String namespace) { this.namespace = namespace; } public String getNamespace() { return namespace; } public Double getExceedCount() { return exceedCount; } public ServerFlowConfig setExceedCount(Double exceedCount) { this.exceedCount = exceedCount; return this; } public Double getMaxOccupyRatio() { return maxOccupyRatio; } public ServerFlowConfig setMaxOccupyRatio(Double maxOccupyRatio) { this.maxOccupyRatio = maxOccupyRatio; return this; } public Integer getIntervalMs() { return intervalMs; } public ServerFlowConfig setIntervalMs(Integer intervalMs) { this.intervalMs = intervalMs; return this; } public Integer getSampleCount() { return sampleCount; } public ServerFlowConfig setSampleCount(Integer sampleCount) { this.sampleCount = sampleCount; return this; } public Double getMaxAllowedQps() { return maxAllowedQps; } public ServerFlowConfig setMaxAllowedQps(Double maxAllowedQps) { this.maxAllowedQps = maxAllowedQps; return this; } @Override public String toString() { return "ServerFlowConfig{" + "namespace='" + namespace + '\'' + ", exceedCount=" + exceedCount + ", maxOccupyRatio=" + maxOccupyRatio + ", intervalMs=" + intervalMs + ", sampleCount=" + sampleCount + ", maxAllowedQps=" + maxAllowedQps + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.config; /** * @author Eric Zhao * @since 1.4.0 */ public class ServerTransportConfig { public static final int DEFAULT_PORT = 18730; public static final int DEFAULT_IDLE_SECONDS = 600; private Integer port; private Integer idleSeconds; public ServerTransportConfig() { this(DEFAULT_PORT, DEFAULT_IDLE_SECONDS); } public ServerTransportConfig(Integer port, Integer idleSeconds) { this.port = port; this.idleSeconds = idleSeconds; } public Integer getPort() { return port; } public ServerTransportConfig setPort(Integer port) { this.port = port; return this; } public Integer getIdleSeconds() { return idleSeconds; } public ServerTransportConfig setIdleSeconds(Integer idleSeconds) { this.idleSeconds = idleSeconds; return this; } @Override public String toString() { return "ServerTransportConfig{" + "port=" + port + ", idleSeconds=" + idleSeconds + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/request/ClusterAppAssignMap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.request; import java.util.Set; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterAppAssignMap { private String machineId; private String ip; private Integer port; private Boolean belongToApp; private Set clientSet; private Set namespaceSet; private Double maxAllowedQps; public String getMachineId() { return machineId; } public ClusterAppAssignMap setMachineId(String machineId) { this.machineId = machineId; return this; } public String getIp() { return ip; } public ClusterAppAssignMap setIp(String ip) { this.ip = ip; return this; } public Integer getPort() { return port; } public ClusterAppAssignMap setPort(Integer port) { this.port = port; return this; } public Set getClientSet() { return clientSet; } public ClusterAppAssignMap setClientSet(Set clientSet) { this.clientSet = clientSet; return this; } public Set getNamespaceSet() { return namespaceSet; } public ClusterAppAssignMap setNamespaceSet(Set namespaceSet) { this.namespaceSet = namespaceSet; return this; } public Boolean getBelongToApp() { return belongToApp; } public ClusterAppAssignMap setBelongToApp(Boolean belongToApp) { this.belongToApp = belongToApp; return this; } public Double getMaxAllowedQps() { return maxAllowedQps; } public ClusterAppAssignMap setMaxAllowedQps(Double maxAllowedQps) { this.maxAllowedQps = maxAllowedQps; return this; } @Override public String toString() { return "ClusterAppAssignMap{" + "machineId='" + machineId + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", belongToApp=" + belongToApp + ", clientSet=" + clientSet + ", namespaceSet=" + namespaceSet + ", maxAllowedQps=" + maxAllowedQps + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/request/ClusterClientModifyRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.request; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterClientModifyRequest implements ClusterModifyRequest { private String app; private String ip; private Integer port; private Integer mode; private ClusterClientConfig clientConfig; @Override public String getApp() { return app; } public ClusterClientModifyRequest setApp(String app) { this.app = app; return this; } @Override public String getIp() { return ip; } public ClusterClientModifyRequest setIp(String ip) { this.ip = ip; return this; } @Override public Integer getPort() { return port; } public ClusterClientModifyRequest setPort(Integer port) { this.port = port; return this; } @Override public Integer getMode() { return mode; } public ClusterClientModifyRequest setMode(Integer mode) { this.mode = mode; return this; } public ClusterClientConfig getClientConfig() { return clientConfig; } public ClusterClientModifyRequest setClientConfig( ClusterClientConfig clientConfig) { this.clientConfig = clientConfig; return this; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/request/ClusterModifyRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.request; /** * @author Eric Zhao * @since 1.4.0 */ public interface ClusterModifyRequest { String getApp(); String getIp(); Integer getPort(); Integer getMode(); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/request/ClusterServerModifyRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.request; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterServerModifyRequest implements ClusterModifyRequest { private String app; private String ip; private Integer port; private Integer mode; private ServerFlowConfig flowConfig; private ServerTransportConfig transportConfig; private Set namespaceSet; @Override public String getApp() { return app; } public ClusterServerModifyRequest setApp(String app) { this.app = app; return this; } @Override public String getIp() { return ip; } public ClusterServerModifyRequest setIp(String ip) { this.ip = ip; return this; } @Override public Integer getPort() { return port; } public ClusterServerModifyRequest setPort(Integer port) { this.port = port; return this; } @Override public Integer getMode() { return mode; } public ClusterServerModifyRequest setMode(Integer mode) { this.mode = mode; return this; } public ServerFlowConfig getFlowConfig() { return flowConfig; } public ClusterServerModifyRequest setFlowConfig( ServerFlowConfig flowConfig) { this.flowConfig = flowConfig; return this; } public ServerTransportConfig getTransportConfig() { return transportConfig; } public ClusterServerModifyRequest setTransportConfig( ServerTransportConfig transportConfig) { this.transportConfig = transportConfig; return this; } public Set getNamespaceSet() { return namespaceSet; } public ClusterServerModifyRequest setNamespaceSet(Set namespaceSet) { this.namespaceSet = namespaceSet; return this; } @Override public String toString() { return "ClusterServerModifyRequest{" + "app='" + app + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", mode=" + mode + ", flowConfig=" + flowConfig + ", transportConfig=" + transportConfig + ", namespaceSet=" + namespaceSet + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/AppClusterClientStateWrapVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.1 */ public class AppClusterClientStateWrapVO { /** * {ip}@{transport_command_port}. */ private String id; private Integer commandPort; private String ip; private ClusterClientStateVO state; public String getId() { return id; } public AppClusterClientStateWrapVO setId(String id) { this.id = id; return this; } public String getIp() { return ip; } public AppClusterClientStateWrapVO setIp(String ip) { this.ip = ip; return this; } public ClusterClientStateVO getState() { return state; } public AppClusterClientStateWrapVO setState(ClusterClientStateVO state) { this.state = state; return this; } public Integer getCommandPort() { return commandPort; } public AppClusterClientStateWrapVO setCommandPort(Integer commandPort) { this.commandPort = commandPort; return this; } @Override public String toString() { return "AppClusterClientStateWrapVO{" + "id='" + id + '\'' + ", commandPort=" + commandPort + ", ip='" + ip + '\'' + ", state=" + state + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/AppClusterServerStateWrapVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.1 */ public class AppClusterServerStateWrapVO { /** * {ip}@{transport_command_port}. */ private String id; private String ip; private Integer port; private Integer connectedCount; private Boolean belongToApp; private ClusterServerStateVO state; public String getId() { return id; } public AppClusterServerStateWrapVO setId(String id) { this.id = id; return this; } public String getIp() { return ip; } public AppClusterServerStateWrapVO setIp(String ip) { this.ip = ip; return this; } public Integer getPort() { return port; } public AppClusterServerStateWrapVO setPort(Integer port) { this.port = port; return this; } public Boolean getBelongToApp() { return belongToApp; } public AppClusterServerStateWrapVO setBelongToApp(Boolean belongToApp) { this.belongToApp = belongToApp; return this; } public Integer getConnectedCount() { return connectedCount; } public AppClusterServerStateWrapVO setConnectedCount(Integer connectedCount) { this.connectedCount = connectedCount; return this; } public ClusterServerStateVO getState() { return state; } public AppClusterServerStateWrapVO setState(ClusterServerStateVO state) { this.state = state; return this; } @Override public String toString() { return "AppClusterServerStateWrapVO{" + "id='" + id + '\'' + ", ip='" + ip + '\'' + ", port='" + port + '\'' + ", belongToApp=" + belongToApp + ", state=" + state + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterClientStateVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterClientInfoVO; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterClientStateVO { /** * Cluster token client state. */ private ClusterClientInfoVO clientConfig; public ClusterClientInfoVO getClientConfig() { return clientConfig; } public ClusterClientStateVO setClientConfig(ClusterClientInfoVO clientConfig) { this.clientConfig = clientConfig; return this; } @Override public String toString() { return "ClusterClientStateVO{" + "clientConfig=" + clientConfig + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterRequestLimitVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterRequestLimitVO { private String namespace; private Double currentQps; private Double maxAllowedQps; public String getNamespace() { return namespace; } public ClusterRequestLimitVO setNamespace(String namespace) { this.namespace = namespace; return this; } public Double getCurrentQps() { return currentQps; } public ClusterRequestLimitVO setCurrentQps(Double currentQps) { this.currentQps = currentQps; return this; } public Double getMaxAllowedQps() { return maxAllowedQps; } public ClusterRequestLimitVO setMaxAllowedQps(Double maxAllowedQps) { this.maxAllowedQps = maxAllowedQps; return this; } @Override public String toString() { return "ClusterRequestLimitVO{" + "namespace='" + namespace + '\'' + ", currentQps=" + currentQps + ", maxAllowedQps=" + maxAllowedQps + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterServerStateVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ConnectionGroupVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterServerStateVO { private String appName; private ServerTransportConfig transport; private ServerFlowConfig flow; private Set namespaceSet; private Integer port; private List connection; private List requestLimitData; private Boolean embedded; public String getAppName() { return appName; } public ClusterServerStateVO setAppName(String appName) { this.appName = appName; return this; } public ServerTransportConfig getTransport() { return transport; } public ClusterServerStateVO setTransport(ServerTransportConfig transport) { this.transport = transport; return this; } public ServerFlowConfig getFlow() { return flow; } public ClusterServerStateVO setFlow(ServerFlowConfig flow) { this.flow = flow; return this; } public Set getNamespaceSet() { return namespaceSet; } public ClusterServerStateVO setNamespaceSet(Set namespaceSet) { this.namespaceSet = namespaceSet; return this; } public Integer getPort() { return port; } public ClusterServerStateVO setPort(Integer port) { this.port = port; return this; } public List getConnection() { return connection; } public ClusterServerStateVO setConnection(List connection) { this.connection = connection; return this; } public List getRequestLimitData() { return requestLimitData; } public ClusterServerStateVO setRequestLimitData(List requestLimitData) { this.requestLimitData = requestLimitData; return this; } public Boolean getEmbedded() { return embedded; } public ClusterServerStateVO setEmbedded(Boolean embedded) { this.embedded = embedded; return this; } @Override public String toString() { return "ClusterServerStateVO{" + "appName='" + appName + '\'' + ", transport=" + transport + ", flow=" + flow + ", namespaceSet=" + namespaceSet + ", port=" + port + ", connection=" + connection + ", requestLimitData=" + requestLimitData + ", embedded=" + embedded + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterStateSimpleEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterStateSimpleEntity { private Integer mode; private Long lastModified; private Boolean clientAvailable; private Boolean serverAvailable; public Integer getMode() { return mode; } public ClusterStateSimpleEntity setMode(Integer mode) { this.mode = mode; return this; } public Long getLastModified() { return lastModified; } public ClusterStateSimpleEntity setLastModified(Long lastModified) { this.lastModified = lastModified; return this; } public Boolean getClientAvailable() { return clientAvailable; } public ClusterStateSimpleEntity setClientAvailable(Boolean clientAvailable) { this.clientAvailable = clientAvailable; return this; } public Boolean getServerAvailable() { return serverAvailable; } public ClusterStateSimpleEntity setServerAvailable(Boolean serverAvailable) { this.serverAvailable = serverAvailable; return this; } @Override public String toString() { return "ClusterStateSimpleEntity{" + "mode=" + mode + ", lastModified=" + lastModified + ", clientAvailable=" + clientAvailable + ", serverAvailable=" + serverAvailable + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStatePairVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterUniversalStatePairVO { private String ip; private Integer commandPort; private ClusterUniversalStateVO state; public ClusterUniversalStatePairVO() {} public ClusterUniversalStatePairVO(String ip, Integer commandPort, ClusterUniversalStateVO state) { this.ip = ip; this.commandPort = commandPort; this.state = state; } public String getIp() { return ip; } public ClusterUniversalStatePairVO setIp(String ip) { this.ip = ip; return this; } public Integer getCommandPort() { return commandPort; } public ClusterUniversalStatePairVO setCommandPort(Integer commandPort) { this.commandPort = commandPort; return this; } public ClusterUniversalStateVO getState() { return state; } public ClusterUniversalStatePairVO setState(ClusterUniversalStateVO state) { this.state = state; return this; } @Override public String toString() { return "ClusterUniversalStatePairVO{" + "ip='" + ip + '\'' + ", commandPort=" + commandPort + ", state=" + state + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStateVO.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao * @since 1.4.0 */ public class ClusterUniversalStateVO { private ClusterStateSimpleEntity stateInfo; private ClusterClientStateVO client; private ClusterServerStateVO server; public ClusterClientStateVO getClient() { return client; } public ClusterUniversalStateVO setClient(ClusterClientStateVO client) { this.client = client; return this; } public ClusterServerStateVO getServer() { return server; } public ClusterUniversalStateVO setServer(ClusterServerStateVO server) { this.server = server; return this; } public ClusterStateSimpleEntity getStateInfo() { return stateInfo; } public ClusterUniversalStateVO setStateInfo( ClusterStateSimpleEntity stateInfo) { this.stateInfo = stateInfo; return this; } @Override public String toString() { return "ClusterUniversalStateVO{" + "stateInfo=" + stateInfo + ", client=" + client + ", server=" + server + '}'; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/MachineInfoVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; /** * @author leyou */ public class MachineInfoVo { private String app; private String hostname; private String ip; private int port; private long heartbeatVersion; private long lastHeartbeat; private boolean healthy; private String version; public static List fromMachineInfoList(List machines) { List list = new ArrayList<>(); for (MachineInfo machine : machines) { list.add(fromMachineInfo(machine)); } return list; } public static MachineInfoVo fromMachineInfo(MachineInfo machine) { MachineInfoVo vo = new MachineInfoVo(); vo.setApp(machine.getApp()); vo.setHostname(machine.getHostname()); vo.setIp(machine.getIp()); vo.setPort(machine.getPort()); vo.setLastHeartbeat(machine.getLastHeartbeat()); vo.setHeartbeatVersion(machine.getHeartbeatVersion()); vo.setVersion(machine.getVersion()); vo.setHealthy(machine.isHealthy()); return vo; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } 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 long getLastHeartbeat() { return lastHeartbeat; } public void setLastHeartbeat(long lastHeartbeat) { this.lastHeartbeat = lastHeartbeat; } public void setHeartbeatVersion(long heartbeatVersion) { this.heartbeatVersion = heartbeatVersion; } public long getHeartbeatVersion() { return heartbeatVersion; } public String getVersion() { return version; } public MachineInfoVo setVersion(String version) { this.version = version; return this; } public boolean isHealthy() { return healthy; } public void setHealthy(boolean healthy) { this.healthy = healthy; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/MetricVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; /** * @author leyou */ public class MetricVo implements Comparable { private Long id; private String app; private Long timestamp; private Long gmtCreate = System.currentTimeMillis(); private String resource; private Long passQps; private Long blockQps; private Long successQps; private Long exceptionQps; /** * average rt */ private Double rt; private Integer count; public MetricVo() { } public static List fromMetricEntities(Collection entities) { List list = new ArrayList<>(); if (entities != null) { for (MetricEntity entity : entities) { list.add(fromMetricEntity(entity)); } } return list; } /** * 保留资源名为identity的结果。 * * @param entities 通过hashCode查找到的MetricEntities * @param identity 真正需要查找的资源名 * @return */ public static List fromMetricEntities(Collection entities, String identity) { List list = new ArrayList<>(); if (entities != null) { for (MetricEntity entity : entities) { if (entity.getResource().equals(identity)) { list.add(fromMetricEntity(entity)); } } } return list; } public static MetricVo fromMetricEntity(MetricEntity entity) { MetricVo vo = new MetricVo(); vo.id = entity.getId(); vo.app = entity.getApp(); vo.timestamp = entity.getTimestamp().getTime(); vo.gmtCreate = entity.getGmtCreate().getTime(); vo.resource = entity.getResource(); vo.passQps = entity.getPassQps(); vo.blockQps = entity.getBlockQps(); vo.successQps = entity.getSuccessQps(); vo.exceptionQps = entity.getExceptionQps(); if (entity.getSuccessQps() != 0) { vo.rt = entity.getRt() / entity.getSuccessQps(); } else { vo.rt = 0D; } vo.count = entity.getCount(); return vo; } public static MetricVo parse(String line) { String[] strs = line.split("\\|"); long timestamp = Long.parseLong(strs[0]); String identity = strs[1]; long passQps = Long.parseLong(strs[2]); long blockQps = Long.parseLong(strs[3]); long exception = Long.parseLong(strs[4]); double rt = Double.parseDouble(strs[5]); long successQps = Long.parseLong(strs[6]); MetricVo vo = new MetricVo(); vo.timestamp = timestamp; vo.resource = identity; vo.passQps = passQps; vo.blockQps = blockQps; vo.successQps = successQps; vo.exceptionQps = exception; vo.rt = rt; vo.count = 1; return vo; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } public Long getGmtCreate() { return gmtCreate; } public void setGmtCreate(Long gmtCreate) { this.gmtCreate = gmtCreate; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Long getPassQps() { return passQps; } public void setPassQps(Long passQps) { this.passQps = passQps; } public Long getBlockQps() { return blockQps; } public void setBlockQps(Long blockQps) { this.blockQps = blockQps; } public Long getSuccessQps() { return successQps; } public void setSuccessQps(Long successQps) { this.successQps = successQps; } public Long getExceptionQps() { return exceptionQps; } public void setExceptionQps(Long exceptionQps) { this.exceptionQps = exceptionQps; } public Double getRt() { return rt; } public void setRt(Double rt) { this.rt = rt; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } @Override public int compareTo(MetricVo o) { return Long.compare(this.timestamp, o.timestamp); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/ResourceVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.dashboard.domain.ResourceTreeNode; /** * @author leyou */ public class ResourceVo { private String parentTtId; private String ttId; private String resource; private Integer threadNum; private Long passQps; private Long blockQps; private Long totalQps; private Long averageRt; private Long passRequestQps; private Long exceptionQps; private Long oneMinutePass; private Long oneMinuteBlock; private Long oneMinuteException; private Long oneMinuteTotal; private boolean visible = true; public ResourceVo() { } public static List fromNodeVoList(List nodeVos) { if (nodeVos == null) { return null; } List list = new ArrayList<>(); for (NodeVo nodeVo : nodeVos) { ResourceVo vo = new ResourceVo(); vo.parentTtId = nodeVo.getParentId(); vo.ttId = nodeVo.getId(); vo.resource = nodeVo.getResource(); vo.threadNum = nodeVo.getThreadNum(); vo.passQps = nodeVo.getPassQps(); vo.blockQps = nodeVo.getBlockQps(); vo.totalQps = nodeVo.getTotalQps(); vo.averageRt = nodeVo.getAverageRt(); vo.exceptionQps = nodeVo.getExceptionQps(); vo.oneMinutePass = nodeVo.getOneMinutePass(); vo.oneMinuteBlock = nodeVo.getOneMinuteBlock(); vo.oneMinuteException = nodeVo.getOneMinuteException(); vo.oneMinuteTotal = nodeVo.getOneMinuteTotal(); list.add(vo); } return list; } public static List fromResourceTreeNode(ResourceTreeNode root) { if (root == null) { return null; } List list = new ArrayList<>(); visit(root, list, false, true); //if(!list.isEmpty()){ // list.remove(0); //} return list; } /** * This node is visible when this.visible==true or one of this's parents is visible, * root node is always invisible. */ private static void visit(ResourceTreeNode node, List list, boolean parentVisible, boolean isRoot) { boolean visible = !isRoot && (node.isVisible() || parentVisible); //boolean visible = node.isVisible(); if (visible) { ResourceVo vo = new ResourceVo(); vo.parentTtId = node.getParentId(); vo.ttId = node.getId(); vo.resource = node.getResource(); vo.threadNum = node.getThreadNum(); vo.passQps = node.getPassQps(); vo.blockQps = node.getBlockQps(); vo.totalQps = node.getTotalQps(); vo.averageRt = node.getAverageRt(); vo.exceptionQps = node.getExceptionQps(); vo.oneMinutePass = node.getOneMinutePass(); vo.oneMinuteBlock = node.getOneMinuteBlock(); vo.oneMinuteException = node.getOneMinuteException(); vo.oneMinuteTotal = node.getOneMinuteTotal(); vo.visible = node.isVisible(); list.add(vo); } for (ResourceTreeNode c : node.getChildren()) { visit(c, list, visible, false); } } public String getParentTtId() { return parentTtId; } public void setParentTtId(String parentTtId) { this.parentTtId = parentTtId; } public String getTtId() { return ttId; } public void setTtId(String ttId) { this.ttId = ttId; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getThreadNum() { return threadNum; } public void setThreadNum(Integer threadNum) { this.threadNum = threadNum; } public Long getPassQps() { return passQps; } public void setPassQps(Long passQps) { this.passQps = passQps; } public Long getBlockQps() { return blockQps; } public void setBlockQps(Long blockQps) { this.blockQps = blockQps; } public Long getTotalQps() { return totalQps; } public void setTotalQps(Long totalQps) { this.totalQps = totalQps; } public Long getAverageRt() { return averageRt; } public void setAverageRt(Long averageRt) { this.averageRt = averageRt; } public Long getPassRequestQps() { return passRequestQps; } public void setPassRequestQps(Long passRequestQps) { this.passRequestQps = passRequestQps; } public Long getExceptionQps() { return exceptionQps; } public void setExceptionQps(Long exceptionQps) { this.exceptionQps = exceptionQps; } public Long getOneMinuteException() { return oneMinuteException; } public void setOneMinuteException(Long oneMinuteException) { this.oneMinuteException = oneMinuteException; } public Long getOneMinutePass() { return oneMinutePass; } public void setOneMinutePass(Long oneMinutePass) { this.oneMinutePass = oneMinutePass; } public Long getOneMinuteBlock() { return oneMinuteBlock; } public void setOneMinuteBlock(Long oneMinuteBlock) { this.oneMinuteBlock = oneMinuteBlock; } public Long getOneMinuteTotal() { return oneMinuteTotal; } public void setOneMinuteTotal(Long oneMinuteTotal) { this.oneMinuteTotal = oneMinuteTotal; } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { this.visible = visible; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/api/AddApiReqVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api; import java.util.List; /** * Value Object for add gateway api. * * @author cdfive * @since 1.7.0 */ public class AddApiReqVo { private String app; private String ip; private Integer port; private String apiName; private List predicateItems; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getApiName() { return apiName; } public void setApiName(String apiName) { this.apiName = apiName; } public List getPredicateItems() { return predicateItems; } public void setPredicateItems(List predicateItems) { this.predicateItems = predicateItems; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/api/ApiPredicateItemVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api; /** * Value Object for add or update gateway api. * * @author cdfive * @since 1.7.0 */ public class ApiPredicateItemVo { /** * The pattern for matching url. */ private String pattern; /** * The matching Strategy in url. Constants are defined in class SentinelGatewayConstants.\ * *
            *
          • 0(URL_MATCH_STRATEGY_EXACT): exact match mode
          • *
          • 1(URL_MATCH_STRATEGY_PREFIX): prefix match mode
          • *
          • 2(URL_MATCH_STRATEGY_REGEX): regex match mode
          • *
          */ private Integer matchStrategy; public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public Integer getMatchStrategy() { return matchStrategy; } public void setMatchStrategy(Integer matchStrategy) { this.matchStrategy = matchStrategy; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/api/UpdateApiReqVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api; import java.util.List; /** * Value Object for update gateway api. * * @author cdfive * @since 1.7.0 */ public class UpdateApiReqVo { private Long id; private String app; private List predicateItems; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public List getPredicateItems() { return predicateItems; } public void setPredicateItems(List predicateItems) { this.predicateItems = predicateItems; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/rule/AddFlowRuleReqVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule; /** * Value Object for add gateway flow rule. * * @author cdfive * @since 1.7.0 */ public class AddFlowRuleReqVo { private String app; private String ip; private Integer port; private String resource; private Integer resourceMode; private Integer grade; private Double count; private Long interval; private Integer intervalUnit; private Integer controlBehavior; private Integer burst; private Integer maxQueueingTimeoutMs; private GatewayParamFlowItemVo paramItem; public String getApp() { return app; } public void setApp(String app) { this.app = app; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getResourceMode() { return resourceMode; } public void setResourceMode(Integer resourceMode) { this.resourceMode = resourceMode; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } public Long getInterval() { return interval; } public void setInterval(Long interval) { this.interval = interval; } public Integer getIntervalUnit() { return intervalUnit; } public void setIntervalUnit(Integer intervalUnit) { this.intervalUnit = intervalUnit; } public Integer getControlBehavior() { return controlBehavior; } public void setControlBehavior(Integer controlBehavior) { this.controlBehavior = controlBehavior; } public Integer getBurst() { return burst; } public void setBurst(Integer burst) { this.burst = burst; } public Integer getMaxQueueingTimeoutMs() { return maxQueueingTimeoutMs; } public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) { this.maxQueueingTimeoutMs = maxQueueingTimeoutMs; } public GatewayParamFlowItemVo getParamItem() { return paramItem; } public void setParamItem(GatewayParamFlowItemVo paramItem) { this.paramItem = paramItem; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/rule/GatewayParamFlowItemVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule; /** * Value Object for add or update gateway flow rule. * * @author cdfive * @since 1.7.0 */ public class GatewayParamFlowItemVo { private Integer parseStrategy; private String fieldName; private String pattern; private Integer matchStrategy; public Integer getParseStrategy() { return parseStrategy; } public void setParseStrategy(Integer parseStrategy) { this.parseStrategy = parseStrategy; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public Integer getMatchStrategy() { return matchStrategy; } public void setMatchStrategy(Integer matchStrategy) { this.matchStrategy = matchStrategy; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/domain/vo/gateway/rule/UpdateFlowRuleReqVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule; /** * Value Object for update gateway flow rule. * * @author cdfive * @since 1.7.0 */ public class UpdateFlowRuleReqVo { private Long id; private String app; private Integer grade; private Double count; private Long interval; private Integer intervalUnit; private Integer controlBehavior; private Integer burst; private Integer maxQueueingTimeoutMs; private GatewayParamFlowItemVo paramItem; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getApp() { return app; } public void setApp(String app) { this.app = app; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public Double getCount() { return count; } public void setCount(Double count) { this.count = count; } public Long getInterval() { return interval; } public void setInterval(Long interval) { this.interval = interval; } public Integer getIntervalUnit() { return intervalUnit; } public void setIntervalUnit(Integer intervalUnit) { this.intervalUnit = intervalUnit; } public Integer getControlBehavior() { return controlBehavior; } public void setControlBehavior(Integer controlBehavior) { this.controlBehavior = controlBehavior; } public Integer getBurst() { return burst; } public void setBurst(Integer burst) { this.burst = burst; } public Integer getMaxQueueingTimeoutMs() { return maxQueueingTimeoutMs; } public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) { this.maxQueueingTimeoutMs = maxQueueingTimeoutMs; } public GatewayParamFlowItemVo getParamItem() { return paramItem; } public void setParamItem(GatewayParamFlowItemVo paramItem) { this.paramItem = paramItem; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/metric/MetricFetcher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.metric; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.nio.charset.Charset; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.impl.nio.reactor.IOReactorConfig; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Fetch metric of machines. * * @author leyou */ @Component public class MetricFetcher { public static final String NO_METRICS = "No metrics"; private static final int HTTP_OK = 200; private static final long MAX_LAST_FETCH_INTERVAL_MS = 1000 * 15; private static final long FETCH_INTERVAL_SECOND = 6; private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset()); private final static String METRIC_URL_PATH = "metric"; private static Logger logger = LoggerFactory.getLogger(MetricFetcher.class); private final long intervalSecond = 1; private Map appLastFetchTime = new ConcurrentHashMap<>(); @Autowired private MetricsRepository metricStore; @Autowired private AppManagement appManagement; private CloseableHttpAsyncClient httpclient; @SuppressWarnings("PMD.ThreadPoolCreationRule") private ScheduledExecutorService fetchScheduleService = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-dashboard-metrics-fetch-task", true)); private ExecutorService fetchService; private ExecutorService fetchWorker; public MetricFetcher() { int cores = Runtime.getRuntime().availableProcessors() * 2; long keepAliveTime = 0; int queueSize = 2048; RejectedExecutionHandler handler = new DiscardPolicy(); fetchService = new ThreadPoolExecutor(cores, cores, keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize), new NamedThreadFactory("sentinel-dashboard-metrics-fetchService", true), handler); fetchWorker = new ThreadPoolExecutor(cores, cores, keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize), new NamedThreadFactory("sentinel-dashboard-metrics-fetchWorker",true), handler); IOReactorConfig ioConfig = IOReactorConfig.custom() .setConnectTimeout(3000) .setSoTimeout(3000) .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2) .build(); httpclient = HttpAsyncClients.custom() .setRedirectStrategy(new DefaultRedirectStrategy() { @Override protected boolean isRedirectable(final String method) { return false; } }).setMaxConnTotal(4000) .setMaxConnPerRoute(1000) .setDefaultIOReactorConfig(ioConfig) .build(); httpclient.start(); start(); } private void start() { fetchScheduleService.scheduleAtFixedRate(() -> { try { fetchAllApp(); } catch (Exception e) { logger.info("fetchAllApp error:", e); } }, 10, intervalSecond, TimeUnit.SECONDS); } private void writeMetric(Map map) { if (map.isEmpty()) { return; } Date date = new Date(); for (MetricEntity entity : map.values()) { entity.setGmtCreate(date); entity.setGmtModified(date); } metricStore.saveAll(map.values()); } /** * Traverse each APP, and then pull the metric of all machines for that APP. */ private void fetchAllApp() { List apps = appManagement.getAppNames(); if (apps == null) { return; } for (final String app : apps) { fetchService.submit(() -> { try { doFetchAppMetric(app); } catch (Exception e) { logger.error("fetchAppMetric error", e); } }); } } /** * fetch metric between [startTime, endTime], both side inclusive */ private void fetchOnce(String app, long startTime, long endTime, int maxWaitSeconds) { if (maxWaitSeconds <= 0) { throw new IllegalArgumentException("maxWaitSeconds must > 0, but " + maxWaitSeconds); } AppInfo appInfo = appManagement.getDetailApp(app); // auto remove for app if (appInfo.isDead()) { logger.info("Dead app removed: {}", app); appManagement.removeApp(app); return; } Set machines = appInfo.getMachines(); logger.debug("enter fetchOnce(" + app + "), machines.size()=" + machines.size() + ", time intervalMs [" + startTime + ", " + endTime + "]"); if (machines.isEmpty()) { return; } final String msg = "fetch"; AtomicLong unhealthy = new AtomicLong(); final AtomicLong success = new AtomicLong(); final AtomicLong fail = new AtomicLong(); long start = System.currentTimeMillis(); /** app_resource_timeSecond -> metric */ final Map metricMap = new ConcurrentHashMap<>(16); final CountDownLatch latch = new CountDownLatch(machines.size()); for (final MachineInfo machine : machines) { // auto remove if (machine.isDead()) { latch.countDown(); appManagement.getDetailApp(app).removeMachine(machine.getIp(), machine.getPort()); logger.info("Dead machine removed: {}:{} of {}", machine.getIp(), machine.getPort(), app); continue; } if (!machine.isHealthy()) { latch.countDown(); unhealthy.incrementAndGet(); continue; } final String url = "http://" + machine.getIp() + ":" + machine.getPort() + "/" + METRIC_URL_PATH + "?startTime=" + startTime + "&endTime=" + endTime + "&refetch=" + false; final HttpGet httpGet = new HttpGet(url); httpGet.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE); httpclient.execute(httpGet, new FutureCallback() { @Override public void completed(final HttpResponse response) { try { handleResponse(response, machine, metricMap); success.incrementAndGet(); } catch (Exception e) { logger.error(msg + " metric " + url + " error:", e); } finally { latch.countDown(); } } @Override public void failed(final Exception ex) { latch.countDown(); fail.incrementAndGet(); httpGet.abort(); if (ex instanceof SocketTimeoutException) { logger.error("Failed to fetch metric from <{}>: socket timeout", url); } else if (ex instanceof ConnectException) { logger.error("Failed to fetch metric from <{}> (ConnectionException: {})", url, ex.getMessage()); } else { logger.error(msg + " metric " + url + " error", ex); } } @Override public void cancelled() { latch.countDown(); fail.incrementAndGet(); httpGet.abort(); } }); } try { latch.await(maxWaitSeconds, TimeUnit.SECONDS); } catch (Exception e) { logger.info(msg + " metric, wait http client error:", e); } //long cost = System.currentTimeMillis() - start; //logger.info("finished " + msg + " metric for " + app + ", time intervalMs [" + startTime + ", " + endTime // + "], total machines=" + machines.size() + ", dead=" + dead + ", fetch success=" // + success + ", fetch fail=" + fail + ", time cost=" + cost + " ms"); writeMetric(metricMap); } private void doFetchAppMetric(final String app) { long now = System.currentTimeMillis(); long lastFetchMs = now - MAX_LAST_FETCH_INTERVAL_MS; if (appLastFetchTime.containsKey(app)) { lastFetchMs = Math.max(lastFetchMs, appLastFetchTime.get(app).get() + 1000); } // trim milliseconds lastFetchMs = lastFetchMs / 1000 * 1000; long endTime = lastFetchMs + FETCH_INTERVAL_SECOND * 1000; if (endTime > now - 1000 * 2) { // too near return; } // update last_fetch in advance. appLastFetchTime.computeIfAbsent(app, a -> new AtomicLong()).set(endTime); final long finalLastFetchMs = lastFetchMs; final long finalEndTime = endTime; try { // do real fetch async fetchWorker.submit(() -> { try { fetchOnce(app, finalLastFetchMs, finalEndTime, 5); } catch (Exception e) { logger.info("fetchOnce(" + app + ") error", e); } }); } catch (Exception e) { logger.info("submit fetchOnce(" + app + ") fail, intervalMs [" + lastFetchMs + ", " + endTime + "]", e); } } private void handleResponse(final HttpResponse response, MachineInfo machine, Map metricMap) throws Exception { int code = response.getStatusLine().getStatusCode(); if (code != HTTP_OK) { return; } Charset charset = null; try { String contentTypeStr = response.getFirstHeader("Content-type").getValue(); if (StringUtil.isNotEmpty(contentTypeStr)) { ContentType contentType = ContentType.parse(contentTypeStr); charset = contentType.getCharset(); } } catch (Exception ignore) { } String body = EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); if (StringUtil.isEmpty(body) || body.startsWith(NO_METRICS)) { //logger.info(machine.getApp() + ":" + machine.getIp() + ":" + machine.getPort() + ", bodyStr is empty"); return; } String[] lines = body.split("\n"); //logger.info(machine.getApp() + ":" + machine.getIp() + ":" + machine.getPort() + // ", bodyStr.length()=" + body.length() + ", lines=" + lines.length); handleBody(lines, machine, metricMap); } private void handleBody(String[] lines, MachineInfo machine, Map map) { //logger.info("handleBody() lines=" + lines.length + ", machine=" + machine); if (lines.length < 1) { return; } for (String line : lines) { try { MetricNode node = MetricNode.fromThinString(line); if (shouldFilterOut(node.getResource())) { continue; } /* * aggregation metrics by app_resource_timeSecond, ignore ip and port. */ String key = buildMetricKey(machine.getApp(), node.getResource(), node.getTimestamp()); MetricEntity metricEntity = map.computeIfAbsent(key, s -> { MetricEntity initMetricEntity = new MetricEntity(); initMetricEntity.setApp(machine.getApp()); initMetricEntity.setTimestamp(new Date(node.getTimestamp())); initMetricEntity.setPassQps(0L); initMetricEntity.setBlockQps(0L); initMetricEntity.setRtAndSuccessQps(0, 0L); initMetricEntity.setExceptionQps(0L); initMetricEntity.setCount(0); initMetricEntity.setResource(node.getResource()); return initMetricEntity; }); metricEntity.addPassQps(node.getPassQps()); metricEntity.addBlockQps(node.getBlockQps()); metricEntity.addRtAndSuccessQps(node.getRt(), node.getSuccessQps()); metricEntity.addExceptionQps(node.getExceptionQps()); metricEntity.addCount(1); } catch (Exception e) { logger.warn("handleBody line exception, machine: {}, line: {}", machine.toLogString(), line); } } } private String buildMetricKey(String app, String resource, long timestamp) { return app + "__" + resource + "__" + (timestamp / 1000); } private boolean shouldFilterOut(String resource) { return RES_EXCLUSION_SET.contains(resource); } private static final Set RES_EXCLUSION_SET = new HashSet() {{ add(Constants.TOTAL_IN_RESOURCE_NAME); add(Constants.SYSTEM_LOAD_RESOURCE_NAME); add(Constants.CPU_USAGE_RESOURCE_NAME); }}; } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/gateway/InMemApiDefinitionStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; import org.springframework.stereotype.Component; import java.util.concurrent.atomic.AtomicLong; /** * Store {@link ApiDefinitionEntity} in memory. * * @author cdfive * @since 1.7.0 */ @Component public class InMemApiDefinitionStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/gateway/InMemGatewayFlowRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; import org.springframework.stereotype.Component; import java.util.concurrent.atomic.AtomicLong; /** * Store {@link GatewayFlowRuleEntity} in memory. * * @author cdfive * @since 1.7.0 */ @Component public class InMemGatewayFlowRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepository.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.metric; import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.TimeUtil; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; /** * Caches metrics data in a period of time in memory. * * @author Carpenter Lee * @author Eric Zhao */ @Component public class InMemoryMetricsRepository implements MetricsRepository { private static final long MAX_METRIC_LIVE_TIME_MS = 1000 * 60 * 5; /** * {@code app -> resource -> timestamp -> metric} */ private Map>> allMetrics = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); @Override public void save(MetricEntity entity) { if (entity == null || StringUtil.isBlank(entity.getApp())) { return; } readWriteLock.writeLock().lock(); try { allMetrics.computeIfAbsent(entity.getApp(), e -> new HashMap<>(16)) .computeIfAbsent(entity.getResource(), e -> new LinkedHashMap() { @Override protected boolean removeEldestEntry(Entry eldest) { // Metric older than {@link #MAX_METRIC_LIVE_TIME_MS} will be removed. return eldest.getKey() < TimeUtil.currentTimeMillis() - MAX_METRIC_LIVE_TIME_MS; } }).put(entity.getTimestamp().getTime(), entity); } finally { readWriteLock.writeLock().unlock(); } } @Override public void saveAll(Iterable metrics) { if (metrics == null) { return; } readWriteLock.writeLock().lock(); try { metrics.forEach(this::save); } finally { readWriteLock.writeLock().unlock(); } } @Override public List queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) { List results = new ArrayList<>(); if (StringUtil.isBlank(app)) { return results; } Map> resourceMap = allMetrics.get(app); if (resourceMap == null) { return results; } LinkedHashMap metricsMap = resourceMap.get(resource); if (metricsMap == null) { return results; } readWriteLock.readLock().lock(); try { for (Entry entry : metricsMap.entrySet()) { if (entry.getKey() >= startTime && entry.getKey() <= endTime) { results.add(entry.getValue()); } } return results; } finally { readWriteLock.readLock().unlock(); } } @Override public List listResourcesOfApp(String app) { List results = new ArrayList<>(); if (StringUtil.isBlank(app)) { return results; } // resource -> timestamp -> metric Map> resourceMap = allMetrics.get(app); if (resourceMap == null) { return results; } final long minTimeMs = System.currentTimeMillis() - 1000 * 60; Map resourceCount = new ConcurrentHashMap<>(32); readWriteLock.readLock().lock(); try { for (Entry> resourceMetrics : resourceMap.entrySet()) { for (Entry metrics : resourceMetrics.getValue().entrySet()) { if (metrics.getKey() < minTimeMs) { continue; } MetricEntity newEntity = metrics.getValue(); if (resourceCount.containsKey(resourceMetrics.getKey())) { MetricEntity oldEntity = resourceCount.get(resourceMetrics.getKey()); oldEntity.addPassQps(newEntity.getPassQps()); oldEntity.addRtAndSuccessQps(newEntity.getRt(), newEntity.getSuccessQps()); oldEntity.addBlockQps(newEntity.getBlockQps()); oldEntity.addExceptionQps(newEntity.getExceptionQps()); oldEntity.addCount(1); } else { resourceCount.put(resourceMetrics.getKey(), MetricEntity.copyOf(newEntity)); } } } // Order by last minute b_qps DESC. return resourceCount.entrySet() .stream() .sorted((o1, o2) -> { MetricEntity e1 = o1.getValue(); MetricEntity e2 = o2.getValue(); int t = e2.getBlockQps().compareTo(e1.getBlockQps()); if (t != 0) { return t; } return e2.getPassQps().compareTo(e1.getPassQps()); }) .map(Entry::getKey) .collect(Collectors.toList()); } finally { readWriteLock.readLock().unlock(); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/MetricsRepository.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.metric; import java.util.List; /** * Repository interface for aggregated metrics data. * * @param type of metrics * @author Eric Zhao */ public interface MetricsRepository { /** * Save the metric to the storage repository. * * @param metric metric data to save */ void save(T metric); /** * Save all metrics to the storage repository. * * @param metrics metrics to save */ void saveAll(Iterable metrics); /** * Get all metrics by {@code appName} and {@code resourceName} between a period of time. * * @param app application name for Sentinel * @param resource resource name * @param startTime start timestamp * @param endTime end timestamp * @return all metrics in query conditions */ List queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime); /** * List resource name of provided application name. * * @param app application name * @return list of resources */ List listResourcesOfApp(String app); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemAuthorityRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import org.springframework.stereotype.Component; /** * In-memory storage for authority rules. * * @author Eric Zhao * @since 0.2.1 */ @Component public class InMemAuthorityRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemDegradeRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import org.springframework.stereotype.Component; /** * @author leyou */ @Component public class InMemDegradeRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; import org.springframework.stereotype.Component; /** * Store {@link FlowRuleEntity} in memory. * * @author leyou */ @Component public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } @Override protected FlowRuleEntity preProcess(FlowRuleEntity entity) { if (entity != null && entity.isClusterMode()) { ClusterFlowConfig config = entity.getClusterConfig(); if (config == null) { config = new ClusterFlowConfig(); entity.setClusterConfig(config); } // Set cluster rule id. config.setFlowId(entity.getId()); } return entity; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemParamFlowRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import org.springframework.stereotype.Component; /** * @author Eric Zhao * @since 0.2.1 */ @Component public class InMemParamFlowRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } @Override protected ParamFlowRuleEntity preProcess(ParamFlowRuleEntity entity) { if (entity != null && entity.isClusterMode()) { ParamFlowClusterConfig config = entity.getClusterConfig(); if (config == null) { config = new ParamFlowClusterConfig(); } // Set cluster rule id. config.setFlowId(entity.getId()); } return entity; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemSystemRuleStore.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity; import org.springframework.stereotype.Component; /** * @author leyou */ @Component public class InMemSystemRuleStore extends InMemoryRuleRepositoryAdapter { private static AtomicLong ids = new AtomicLong(0); @Override protected long nextId() { return ids.incrementAndGet(); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/InMemoryRuleRepositoryAdapter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author leyou */ public abstract class InMemoryRuleRepositoryAdapter implements RuleRepository { /** * {@code >} */ private Map> machineRules = new ConcurrentHashMap<>(16); private Map allRules = new ConcurrentHashMap<>(16); private Map> appRules = new ConcurrentHashMap<>(16); private static final int MAX_RULES_SIZE = 10000; @Override public T save(T entity) { if (entity.getId() == null) { entity.setId(nextId()); } T processedEntity = preProcess(entity); if (processedEntity != null) { allRules.put(processedEntity.getId(), processedEntity); machineRules.computeIfAbsent(MachineInfo.of(processedEntity.getApp(), processedEntity.getIp(), processedEntity.getPort()), e -> new ConcurrentHashMap<>(32)) .put(processedEntity.getId(), processedEntity); appRules.computeIfAbsent(processedEntity.getApp(), v -> new ConcurrentHashMap<>(32)) .put(processedEntity.getId(), processedEntity); } return processedEntity; } @Override public List saveAll(List rules) { // TODO: check here. allRules.clear(); machineRules.clear(); appRules.clear(); if (rules == null) { return null; } List savedRules = new ArrayList<>(rules.size()); for (T rule : rules) { savedRules.add(save(rule)); } return savedRules; } @Override public T delete(Long id) { T entity = allRules.remove(id); if (entity != null) { if (appRules.get(entity.getApp()) != null) { appRules.get(entity.getApp()).remove(id); } machineRules.get(MachineInfo.of(entity.getApp(), entity.getIp(), entity.getPort())).remove(id); } return entity; } @Override public T findById(Long id) { return allRules.get(id); } @Override public List findAllByMachine(MachineInfo machineInfo) { Map entities = machineRules.get(machineInfo); if (entities == null) { return new ArrayList<>(); } return new ArrayList<>(entities.values()); } @Override public List findAllByApp(String appName) { AssertUtil.notEmpty(appName, "appName cannot be empty"); Map entities = appRules.get(appName); if (entities == null) { return new ArrayList<>(); } return new ArrayList<>(entities.values()); } public void clearAll() { allRules.clear(); machineRules.clear(); appRules.clear(); } protected T preProcess(T entity) { return entity; } /** * Get next unused id. * * @return next unused id */ abstract protected long nextId(); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/rule/RuleRepository.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.rule; import java.util.List; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; /** * Interface to store and find rules. * * @author leyou */ public interface RuleRepository { /** * Save one. * * @param entity * @return */ T save(T entity); /** * Save all. * * @param rules * @return rules saved. */ List saveAll(List rules); /** * Delete by id * * @param id * @return entity deleted */ T delete(ID id); /** * Find by id. * * @param id * @return */ T findById(ID id); /** * Find all by machine. * * @param machineInfo * @return */ List findAllByMachine(MachineInfo machineInfo); /** * Find all by application. * * @param appName valid app name * @return all rules of the application * @since 1.4.0 */ List findAllByApp(String appName); ///** // * Find all by app and enable switch. // * @param app // * @param enable // * @return // */ //List findAllByAppAndEnable(String app, boolean enable); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/DynamicRuleProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule; /** * @author Eric Zhao * @since 1.4.0 */ public interface DynamicRuleProvider { T getRules(String appName) throws Exception; } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/DynamicRulePublisher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule; /** * @author Eric Zhao * @since 1.4.0 */ public interface DynamicRulePublisher { /** * Publish rules to remote rule configuration center for given application name. * * @param app app name * @param rules list of rules to push * @throws Exception if some error occurs */ void publish(String app, T rules) throws Exception; } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author Eric Zhao */ @Component("flowRuleDefaultProvider") public class FlowRuleApiProvider implements DynamicRuleProvider> { @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; @Override public List getRules(String appName) throws Exception { if (StringUtil.isBlank(appName)) { return new ArrayList<>(); } List list = appManagement.getDetailApp(appName).getMachines() .stream() .filter(MachineInfo::isHealthy) .sorted((e1, e2) -> Long.compare(e2.getLastHeartbeat(), e1.getLastHeartbeat())).collect(Collectors.toList()); if (list.isEmpty()) { return new ArrayList<>(); } else { MachineInfo machine = list.get(0); return sentinelApiClient.fetchFlowRuleOfMachine(machine.getApp(), machine.getIp(), machine.getPort()); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author Eric Zhao * @since 1.4.0 */ @Component("flowRuleDefaultPublisher") public class FlowRuleApiPublisher implements DynamicRulePublisher> { @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; @Override public void publish(String app, List rules) throws Exception { if (StringUtil.isBlank(app)) { return; } if (rules == null) { return; } Set set = appManagement.getDetailApp(app).getMachines(); for (MachineInfo machine : set) { if (!machine.isHealthy()) { continue; } // TODO: parse the results sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules); } } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/service/ClusterAssignService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.service; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterAppAssignMap; /** * @author Eric Zhao * @since 1.4.1 */ public interface ClusterAssignService { /** * Unbind a specific cluster server and its clients. * * @param app app name * @param machineId valid machine ID ({@code host@commandPort}) * @return assign result */ ClusterAppAssignResultVO unbindClusterServer(String app, String machineId); /** * Unbind a set of cluster servers and its clients. * * @param app app name * @param machineIdSet set of valid machine ID ({@code host@commandPort}) * @return assign result */ ClusterAppAssignResultVO unbindClusterServers(String app, Set machineIdSet); /** * Apply cluster server and client assignment for provided app. * * @param app app name * @param clusterMap cluster assign map (server -> clients) * @param remainingSet unassigned set of machine ID * @return assign result */ ClusterAppAssignResultVO applyAssignToApp(String app, List clusterMap, Set remainingSet); } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/service/ClusterAssignServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.service; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Tuple2; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterAppAssignMap; import com.alibaba.csp.sentinel.dashboard.util.MachineUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author Eric Zhao * @since 1.4.1 */ @Service public class ClusterAssignServiceImpl implements ClusterAssignService { private final Logger LOGGER = LoggerFactory.getLogger(ClusterAssignServiceImpl.class); @Autowired private SentinelApiClient sentinelApiClient; @Autowired private ClusterConfigService clusterConfigService; private boolean isMachineInApp(/*@NonEmpty*/ String machineId) { return machineId.contains(":"); } private ClusterAppAssignResultVO handleUnbindClusterServerNotInApp(String app, String machineId) { Set failedSet = new HashSet<>(); try { List list = clusterConfigService.getClusterUniversalState(app) .get(10, TimeUnit.SECONDS); Set toModifySet = list.stream() .filter(e -> e.getState().getStateInfo().getMode() == ClusterStateManager.CLUSTER_CLIENT) .filter(e -> machineId.equals(e.getState().getClient().getClientConfig().getServerHost() + ':' + e.getState().getClient().getClientConfig().getServerPort())) .map(e -> e.getIp() + '@' + e.getCommandPort()) .collect(Collectors.toSet()); // Modify mode to NOT-STARTED for all associated token clients. modifyToNonStarted(toModifySet, failedSet); } catch (Exception ex) { Throwable e = ex instanceof ExecutionException ? ex.getCause() : ex; LOGGER.error("Failed to unbind machine <{}>", machineId, e); failedSet.add(machineId); } return new ClusterAppAssignResultVO() .setFailedClientSet(failedSet) .setFailedServerSet(new HashSet<>()); } private void modifyToNonStarted(Set toModifySet, Set failedSet) { toModifySet.parallelStream() .map(MachineUtils::parseCommandIpAndPort) .filter(Optional::isPresent) .map(Optional::get) .map(e -> { CompletableFuture f = modifyMode(e.r1, e.r2, ClusterStateManager.CLUSTER_NOT_STARTED); return Tuple2.of(e.r1 + '@' + e.r2, f); }) .forEach(f -> handleFutureSync(f, failedSet)); } @Override public ClusterAppAssignResultVO unbindClusterServer(String app, String machineId) { AssertUtil.assertNotBlank(app, "app cannot be blank"); AssertUtil.assertNotBlank(machineId, "machineId cannot be blank"); if (isMachineInApp(machineId)) { return handleUnbindClusterServerNotInApp(app, machineId); } Set failedSet = new HashSet<>(); try { ClusterGroupEntity entity = clusterConfigService.getClusterUniversalStateForAppMachine(app, machineId) .get(10, TimeUnit.SECONDS); Set toModifySet = new HashSet<>(); toModifySet.add(machineId); if (entity.getClientSet() != null) { toModifySet.addAll(entity.getClientSet()); } // Modify mode to NOT-STARTED for all chosen token servers and associated token clients. modifyToNonStarted(toModifySet, failedSet); } catch (Exception ex) { Throwable e = ex instanceof ExecutionException ? ex.getCause() : ex; LOGGER.error("Failed to unbind machine <{}>", machineId, e); failedSet.add(machineId); } return new ClusterAppAssignResultVO() .setFailedClientSet(failedSet) .setFailedServerSet(new HashSet<>()); } @Override public ClusterAppAssignResultVO unbindClusterServers(String app, Set machineIdSet) { AssertUtil.assertNotBlank(app, "app cannot be blank"); AssertUtil.isTrue(machineIdSet != null && !machineIdSet.isEmpty(), "machineIdSet cannot be empty"); ClusterAppAssignResultVO result = new ClusterAppAssignResultVO() .setFailedClientSet(new HashSet<>()) .setFailedServerSet(new HashSet<>()); for (String machineId : machineIdSet) { ClusterAppAssignResultVO resultVO = unbindClusterServer(app, machineId); result.getFailedClientSet().addAll(resultVO.getFailedClientSet()); result.getFailedServerSet().addAll(resultVO.getFailedServerSet()); } return result; } @Override public ClusterAppAssignResultVO applyAssignToApp(String app, List clusterMap, Set remainingSet) { AssertUtil.assertNotBlank(app, "app cannot be blank"); AssertUtil.notNull(clusterMap, "clusterMap cannot be null"); Set failedServerSet = new HashSet<>(); Set failedClientSet = new HashSet<>(); // Assign server and apply config. clusterMap.stream() .filter(Objects::nonNull) .filter(ClusterAppAssignMap::getBelongToApp) .map(e -> { String ip = e.getIp(); int commandPort = parsePort(e); CompletableFuture f = modifyMode(ip, commandPort, ClusterStateManager.CLUSTER_SERVER) .thenCompose(v -> applyServerConfigChange(app, ip, commandPort, e)); return Tuple2.of(e.getMachineId(), f); }) .forEach(t -> handleFutureSync(t, failedServerSet)); // Assign client of servers and apply config. clusterMap.parallelStream() .filter(Objects::nonNull) .forEach(e -> applyAllClientConfigChange(app, e, failedClientSet)); // Unbind remaining (unassigned) machines. applyAllRemainingMachineSet(app, remainingSet, failedClientSet); return new ClusterAppAssignResultVO() .setFailedClientSet(failedClientSet) .setFailedServerSet(failedServerSet); } private void applyAllRemainingMachineSet(String app, Set remainingSet, Set failedSet) { if (remainingSet == null || remainingSet.isEmpty()) { return; } remainingSet.parallelStream() .filter(Objects::nonNull) .map(MachineUtils::parseCommandIpAndPort) .filter(Optional::isPresent) .map(Optional::get) .map(ipPort -> { String ip = ipPort.r1; int commandPort = ipPort.r2; CompletableFuture f = modifyMode(ip, commandPort, ClusterStateManager.CLUSTER_NOT_STARTED); return Tuple2.of(ip + '@' + commandPort, f); }) .forEach(t -> handleFutureSync(t, failedSet)); } private void applyAllClientConfigChange(String app, ClusterAppAssignMap assignMap, Set failedSet) { Set clientSet = assignMap.getClientSet(); if (clientSet == null || clientSet.isEmpty()) { return; } final String serverIp = assignMap.getIp(); final int serverPort = assignMap.getPort(); clientSet.stream() .map(MachineUtils::parseCommandIpAndPort) .filter(Optional::isPresent) .map(Optional::get) .map(ipPort -> { CompletableFuture f = sentinelApiClient .modifyClusterMode(ipPort.r1, ipPort.r2, ClusterStateManager.CLUSTER_CLIENT) .thenCompose(v -> sentinelApiClient.modifyClusterClientConfig(app, ipPort.r1, ipPort.r2, new ClusterClientConfig().setRequestTimeout(20) .setServerHost(serverIp) .setServerPort(serverPort) )); return Tuple2.of(ipPort.r1 + '@' + ipPort.r2, f); }) .forEach(t -> handleFutureSync(t, failedSet)); } private void handleFutureSync(Tuple2> t, Set failedSet) { try { t.r2.get(10, TimeUnit.SECONDS); } catch (Exception ex) { if (ex instanceof ExecutionException) { LOGGER.error("Request for <{}> failed", t.r1, ex.getCause()); } else { LOGGER.error("Request for <{}> failed", t.r1, ex); } failedSet.add(t.r1); } } private CompletableFuture applyServerConfigChange(String app, String ip, int commandPort, ClusterAppAssignMap assignMap) { ServerTransportConfig transportConfig = new ServerTransportConfig() .setPort(assignMap.getPort()) .setIdleSeconds(600); return sentinelApiClient.modifyClusterServerTransportConfig(app, ip, commandPort, transportConfig) .thenCompose(v -> applyServerFlowConfigChange(app, ip, commandPort, assignMap)) .thenCompose(v -> applyServerNamespaceSetConfig(app, ip, commandPort, assignMap)); } private CompletableFuture applyServerFlowConfigChange(String app, String ip, int commandPort, ClusterAppAssignMap assignMap) { Double maxAllowedQps = assignMap.getMaxAllowedQps(); if (maxAllowedQps == null || maxAllowedQps <= 0 || maxAllowedQps > 20_0000) { return CompletableFuture.completedFuture(null); } return sentinelApiClient.modifyClusterServerFlowConfig(app, ip, commandPort, new ServerFlowConfig().setMaxAllowedQps(maxAllowedQps)); } private CompletableFuture applyServerNamespaceSetConfig(String app, String ip, int commandPort, ClusterAppAssignMap assignMap) { Set namespaceSet = assignMap.getNamespaceSet(); if (namespaceSet == null || namespaceSet.isEmpty()) { return CompletableFuture.completedFuture(null); } return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, commandPort, namespaceSet); } private CompletableFuture modifyMode(String ip, int port, int mode) { return sentinelApiClient.modifyClusterMode(ip, port, mode); } private int parsePort(ClusterAppAssignMap assignMap) { return MachineUtils.parseCommandPort(assignMap.getMachineId()) .orElse(ServerTransportConfig.DEFAULT_PORT); } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/service/ClusterConfigService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.service; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest; import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils; import com.alibaba.csp.sentinel.dashboard.util.ClusterEntityUtils; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterClientModifyRequest; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author Eric Zhao * @since 1.4.0 */ @Service public class ClusterConfigService { @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AppManagement appManagement; public CompletableFuture modifyClusterClientConfig(ClusterClientModifyRequest request) { if (notClientRequestValid(request)) { throw new IllegalArgumentException("Invalid request"); } String app = request.getApp(); String ip = request.getIp(); int port = request.getPort(); return sentinelApiClient.modifyClusterClientConfig(app, ip, port, request.getClientConfig()) .thenCompose(v -> sentinelApiClient.modifyClusterMode(ip, port, ClusterStateManager.CLUSTER_CLIENT)); } private boolean notClientRequestValid(/*@NonNull */ ClusterClientModifyRequest request) { ClusterClientConfig config = request.getClientConfig(); return config == null || StringUtil.isEmpty(config.getServerHost()) || config.getServerPort() == null || config.getServerPort() <= 0 || config.getRequestTimeout() == null || config.getRequestTimeout() <= 0; } public CompletableFuture modifyClusterServerConfig(ClusterServerModifyRequest request) { ServerTransportConfig transportConfig = request.getTransportConfig(); ServerFlowConfig flowConfig = request.getFlowConfig(); Set namespaceSet = request.getNamespaceSet(); if (invalidTransportConfig(transportConfig)) { throw new IllegalArgumentException("Invalid transport config in request"); } if (invalidFlowConfig(flowConfig)) { throw new IllegalArgumentException("Invalid flow config in request"); } if (namespaceSet == null) { throw new IllegalArgumentException("namespace set cannot be null"); } String app = request.getApp(); String ip = request.getIp(); int port = request.getPort(); return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, port, namespaceSet) .thenCompose(v -> sentinelApiClient.modifyClusterServerTransportConfig(app, ip, port, transportConfig)) .thenCompose(v -> sentinelApiClient.modifyClusterServerFlowConfig(app, ip, port, flowConfig)) .thenCompose(v -> sentinelApiClient.modifyClusterMode(ip, port, ClusterStateManager.CLUSTER_SERVER)); } /** * Get cluster state list of all available machines of provided application. * * @param app application name * @return cluster state list of all available machines of the application * @since 1.4.1 */ public CompletableFuture> getClusterUniversalState(String app) { if (StringUtil.isBlank(app)) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty")); } AppInfo appInfo = appManagement.getDetailApp(app); if (appInfo == null || appInfo.getMachines() == null) { return CompletableFuture.completedFuture(new ArrayList<>()); } List> futures = appInfo.getMachines().stream() .filter(e -> e.isHealthy()) .map(machine -> getClusterUniversalState(app, machine.getIp(), machine.getPort()) .thenApply(e -> new ClusterUniversalStatePairVO(machine.getIp(), machine.getPort(), e))) .collect(Collectors.toList()); return AsyncUtils.sequenceSuccessFuture(futures); } public CompletableFuture getClusterUniversalStateForAppMachine(String app, String machineId) { if (StringUtil.isBlank(app)) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty")); } AppInfo appInfo = appManagement.getDetailApp(app); if (appInfo == null || appInfo.getMachines() == null) { return AsyncUtils.newFailedFuture(new IllegalArgumentException("app does not have machines")); } boolean machineOk = appInfo.getMachines().stream() .filter(e -> e.isHealthy()) .map(e -> e.getIp() + '@' + e.getPort()) .anyMatch(e -> e.equals(machineId)); if (!machineOk) { return AsyncUtils.newFailedFuture(new IllegalStateException("machine does not exist or disconnected")); } return getClusterUniversalState(app) .thenApply(ClusterEntityUtils::wrapToClusterGroup) .thenCompose(e -> e.stream() .filter(e1 -> e1.getMachineId().equals(machineId)) .findAny() .map(CompletableFuture::completedFuture) .orElse(AsyncUtils.newFailedFuture(new IllegalStateException("not a server: " + machineId))) ); } public CompletableFuture getClusterUniversalState(String app, String ip, int port) { return sentinelApiClient.fetchClusterMode(ip, port) .thenApply(e -> new ClusterUniversalStateVO().setStateInfo(e)) .thenCompose(vo -> { if (vo.getStateInfo().getClientAvailable()) { return sentinelApiClient.fetchClusterClientInfoAndConfig(ip, port) .thenApply(cc -> vo.setClient(new ClusterClientStateVO().setClientConfig(cc))); } else { return CompletableFuture.completedFuture(vo); } }).thenCompose(vo -> { if (vo.getStateInfo().getServerAvailable()) { return sentinelApiClient.fetchClusterServerBasicInfo(ip, port) .thenApply(vo::setServer); } else { return CompletableFuture.completedFuture(vo); } }); } private boolean invalidTransportConfig(ServerTransportConfig transportConfig) { return transportConfig == null || transportConfig.getPort() == null || transportConfig.getPort() <= 0 || transportConfig.getIdleSeconds() == null || transportConfig.getIdleSeconds() <= 0; } private boolean invalidFlowConfig(ServerFlowConfig flowConfig) { return flowConfig == null || flowConfig.getSampleCount() == null || flowConfig.getSampleCount() <= 0 || flowConfig.getIntervalMs() == null || flowConfig.getIntervalMs() <= 0 || flowConfig.getIntervalMs() % flowConfig.getSampleCount() != 0 || flowConfig.getMaxAllowedQps() == null || flowConfig.getMaxAllowedQps() < 0; } } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/AsyncUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.util; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Eric Zhao * @since 1.4.1 */ public final class AsyncUtils { private static final Logger LOG = LoggerFactory.getLogger(AsyncUtils.class); public static CompletableFuture newFailedFuture(Throwable ex) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(ex); return future; } public static CompletableFuture> sequenceFuture(List> futures) { return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenApply(v -> futures.stream() .map(AsyncUtils::getValue) .filter(Objects::nonNull) .collect(Collectors.toList()) ); } public static CompletableFuture> sequenceSuccessFuture(List> futures) { return CompletableFuture.supplyAsync(() -> futures.parallelStream() .map(AsyncUtils::getValue) .filter(Objects::nonNull) .collect(Collectors.toList()) ); } public static T getValue(CompletableFuture future) { try { return future.get(10, TimeUnit.SECONDS); } catch (Exception ex) { LOG.error("getValue for async result failed", ex); } return null; } public static boolean isSuccessFuture(CompletableFuture future) { return future.isDone() && !future.isCompletedExceptionally() && !future.isCancelled(); } private AsyncUtils() {} } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/ClusterEntityUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.util; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ConnectionGroupVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; /** * @author Eric Zhao * @since 1.4.1 */ public final class ClusterEntityUtils { public static List wrapToAppClusterServerState( List list) { if (list == null || list.isEmpty()) { return new ArrayList<>(); } Map map = new HashMap<>(); Set tokenServerSet = new HashSet<>(); // Handle token servers that belong to current app. for (ClusterUniversalStatePairVO stateVO : list) { int mode = stateVO.getState().getStateInfo().getMode(); if (mode == ClusterStateManager.CLUSTER_SERVER) { String ip = stateVO.getIp(); String serverId = ip + '@' + stateVO.getCommandPort(); ClusterServerStateVO serverStateVO = stateVO.getState().getServer(); map.computeIfAbsent(serverId, v -> new AppClusterServerStateWrapVO() .setId(serverId) .setIp(ip) .setPort(serverStateVO.getPort()) .setState(serverStateVO) .setBelongToApp(true) .setConnectedCount(serverStateVO.getConnection().stream() .mapToInt(ConnectionGroupVO::getConnectedCount) .sum() ) ); tokenServerSet.add(ip + ":" + serverStateVO.getPort()); } } // Handle token servers from other app. for (ClusterUniversalStatePairVO stateVO : list) { int mode = stateVO.getState().getStateInfo().getMode(); if (mode == ClusterStateManager.CLUSTER_CLIENT) { ClusterClientStateVO clientState = stateVO.getState().getClient(); if (clientState == null) { continue; } String serverIp = clientState.getClientConfig().getServerHost(); int serverPort = clientState.getClientConfig().getServerPort(); if (tokenServerSet.contains(serverIp + ":" + serverPort)) { continue; } // We are not able to get the commandPort of foreign token server directly. String serverId = String.format("%s:%d", serverIp, serverPort); map.computeIfAbsent(serverId, v -> new AppClusterServerStateWrapVO() .setId(serverId) .setIp(serverIp) .setPort(serverPort) .setBelongToApp(false) ); } } return new ArrayList<>(map.values()); } public static List wrapToAppClusterClientState( List list) { if (list == null || list.isEmpty()) { return new ArrayList<>(); } Map map = new HashMap<>(); for (ClusterUniversalStatePairVO stateVO : list) { int mode = stateVO.getState().getStateInfo().getMode(); if (mode == ClusterStateManager.CLUSTER_CLIENT) { String ip = stateVO.getIp(); String clientId = ip + '@' + stateVO.getCommandPort(); ClusterClientStateVO clientStateVO = stateVO.getState().getClient(); map.computeIfAbsent(clientId, v -> new AppClusterClientStateWrapVO() .setId(clientId) .setIp(ip) .setState(clientStateVO) .setCommandPort(stateVO.getCommandPort()) ); } } return new ArrayList<>(map.values()); } public static List wrapToClusterGroup(List list) { if (list == null || list.isEmpty()) { return new ArrayList<>(); } Map map = new HashMap<>(); for (ClusterUniversalStatePairVO stateVO : list) { int mode = stateVO.getState().getStateInfo().getMode(); String ip = stateVO.getIp(); if (mode == ClusterStateManager.CLUSTER_SERVER) { String serverAddress = getIp(ip); int port = stateVO.getState().getServer().getPort(); String targetAddress = serverAddress + ":" + port; map.computeIfAbsent(targetAddress, v -> new ClusterGroupEntity() .setBelongToApp(true).setMachineId(ip + '@' + stateVO.getCommandPort()) .setIp(ip).setPort(port) ); } } for (ClusterUniversalStatePairVO stateVO : list) { int mode = stateVO.getState().getStateInfo().getMode(); String ip = stateVO.getIp(); if (mode == ClusterStateManager.CLUSTER_CLIENT) { String targetServer = stateVO.getState().getClient().getClientConfig().getServerHost(); Integer targetPort = stateVO.getState().getClient().getClientConfig().getServerPort(); if (StringUtil.isBlank(targetServer) || targetPort == null || targetPort <= 0) { continue; } String targetAddress = targetServer + ":" + targetPort; ClusterGroupEntity group = map.computeIfAbsent(targetAddress, v -> new ClusterGroupEntity() .setBelongToApp(true).setMachineId(targetServer) .setIp(targetServer).setPort(targetPort) ); group.getClientSet().add(ip + '@' + stateVO.getCommandPort()); } } return new ArrayList<>(map.values()); } private static String getIp(String str) { if (str.contains(":")) { return str.split(":")[0]; } return str; } private ClusterEntityUtils() {} } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/MachineUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.util; import java.util.Optional; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Tuple2; /** * @author Eric Zhao */ public final class MachineUtils { public static Optional parseCommandPort(String machineIp) { try { if (!machineIp.contains("@")) { return Optional.empty(); } String[] str = machineIp.split("@"); if (str.length <= 1) { return Optional.empty(); } return Optional.of(Integer.parseInt(str[1])); } catch (Exception ex) { return Optional.empty(); } } public static Optional> parseCommandIpAndPort(String machineIp) { try { if (StringUtil.isEmpty(machineIp) || !machineIp.contains("@")) { return Optional.empty(); } String[] str = machineIp.split("@"); if (str.length <= 1) { return Optional.empty(); } return Optional.of(Tuple2.of(str[0], Integer.parseInt(str[1]))); } catch (Exception ex) { return Optional.empty(); } } private MachineUtils() {} } ================================================ FILE: sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.util; import java.util.Optional; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; /** * Util class for parsing version. * * @author Eric Zhao * @since 0.2.1 */ public final class VersionUtils { /** * Parse version of Sentinel from raw string. * * @param verStr version string * @return parsed {@link SentinelVersion} if the version is valid; empty if * there is something wrong with the format */ public static Optional parseVersion(String verStr) { if (StringUtil.isBlank(verStr)) { return Optional.empty(); } try { String versionFull = verStr; SentinelVersion version = new SentinelVersion(); // postfix int index = versionFull.indexOf("-"); if (index == 0) { // Start with "-" return Optional.empty(); } if (index == versionFull.length() - 1) { // End with "-" } else if (index > 0) { version.setPostfix(versionFull.substring(index + 1)); } if (index >= 0) { versionFull = versionFull.substring(0, index); } // x.x.x int segment = 0; int[] ver = new int[3]; while (segment < ver.length) { index = versionFull.indexOf('.'); if (index < 0) { if (versionFull.length() > 0) { ver[segment] = Integer.valueOf(versionFull); } break; } ver[segment] = Integer.valueOf(versionFull.substring(0, index)); versionFull = versionFull.substring(index + 1); segment ++; } if (ver[0] < 1) { // Wrong format, return empty. return Optional.empty(); } else { return Optional.of(version .setMajorVersion(ver[0]) .setMinorVersion(ver[1]) .setFixVersion(ver[2])); } } catch (Exception ex) { // Parse fail, return empty. return Optional.empty(); } } private VersionUtils() {} } ================================================ FILE: sentinel-dashboard/src/main/resources/application.properties ================================================ #spring settings server.servlet.encoding.force=true server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true #cookie name setting server.servlet.session.cookie.name=sentinel_dashboard_cookie #logging settings logging.level.org.springframework.web=INFO logging.file.name=${user.home}/logs/csp/sentinel-dashboard.log logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n #logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n #auth settings auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine,/version auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff,png # If auth.enabled=false, Sentinel console disable login auth.username=sentinel auth.password=sentinel # Inject the dashboard version. It's required to enable # filtering in pom.xml for this resource file. sentinel.dashboard.version=@project.version@ ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/.gitignore ================================================ node_modules/ tmp/ ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/.jshintrc ================================================ { /* * ENVIRONMENTS * ================= */ // Define globals exposed by modern browsers. "browser": true, // Define globals exposed by jQuery. "jquery": true, // Define globals exposed by Node.js. "node": true, // Allow ES6. "esversion": 6, /* * ENFORCING OPTIONS * ================= */ // Force all variable names to use either camelCase style or UPPER_CASE // with underscores. "camelcase": true, // Prohibit use of == and != in favor of === and !==. "eqeqeq": true, // Enforce tab width of 2 spaces. "indent": 2, // Prohibit use of a variable before it is defined. "latedef": true, // Enforce line length to 100 characters "maxlen": 100, // Require capitalized names for constructor functions. "newcap": true, // Enforce use of single quotation marks for strings. "quotmark": "single", // Enforce placing 'use strict' at the top function scope // 前端项目中外层使用 strict 即可,覆盖此条规则 "strict": false, // Prohibit use of explicitly undeclared variables. "undef": true, // Warn when variables are defined but never used. "unused": true, /* * RELAXING OPTIONS * ================= */ // Suppress warnings about == null comparisons. "eqnull": true, "globals": { "$": false, "angular": false } } ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/README.md ================================================ # Sentinel Dashboard Frontend ## Env Requirement - Node.js > 6.x - Node.js < 12.x ## Code Guide - [Code Style Guide for HTML/CSS](https://codeguide.bootcss.com/) - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) ## Install Packages ```shell npm install ``` ## Start Development ```shell npm start ``` ## Build for production ```shell npm run build ``` ## Credit - [sb-admin-angular](https://github.com/start-angular/sb-admin-angular) ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/README_zh.md ================================================ # Sentinel Dashboard Frontend ## 环境要求 - Node.js > 6.x - Node.js < 12.x ## 编码规范 - HTML/CSS 遵循 [Bootstrap 编码规范](https://codeguide.bootcss.com/) - JavaScript 遵循 [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) 以及最新的 ES 6 标准 ## 安装依赖 ```shell npm i ``` ## 开始本地开发 ```shell npm start ``` ## 构建前端资源 ```shell npm run build ``` ## Credit - [sb-admin-angular](https://github.com/start-angular/sb-admin-angular) ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js ================================================ 'use strict'; /** * @ngdoc overview * @name sentinelDashboardApp * @description * # sentinelDashboardApp * * Main module of the application. */ angular .module('sentinelDashboardApp', [ 'oc.lazyLoad', 'ui.router', 'ui.bootstrap', 'angular-loading-bar', 'ngDialog', 'ui.bootstrap.datetimepicker', 'ui-notification', 'rzTable', 'angular-clipboard', 'selectize', 'angularUtils.directives.dirPagination' ]) .factory('AuthInterceptor', ['$window', '$state', function ($window, $state) { var authInterceptor = { 'responseError' : function(response) { if (response.status === 401) { // If not auth, clear session in localStorage and jump to the login page $window.localStorage.removeItem('session_sentinel_admin'); $state.go('login'); } return response; }, 'response' : function(response) { return response; }, 'request' : function(config) { // Resolved resource loading failure after configuring ContextPath var baseUrl = $window.document.getElementsByTagName('base')[0].href; config.url = baseUrl + config.url; return config; }, 'requestError' : function(config){ return config; } }; return authInterceptor; }]) .config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider, $httpProvider) { $httpProvider.interceptors.push('AuthInterceptor'); $ocLazyLoadProvider.config({ debug: false, events: true, }); $urlRouterProvider.otherwise('/dashboard/home'); $stateProvider .state('login', { url: '/login', templateUrl: 'app/views/login.html', controller: 'LoginCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/login.js', ] }); }] } }) .state('dashboard', { url: '/dashboard', templateUrl: 'app/views/dashboard/main.html', resolve: { loadMyDirectives: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load( { name: 'sentinelDashboardApp', files: [ 'app/scripts/directives/header/header.js', 'app/scripts/directives/sidebar/sidebar.js', 'app/scripts/directives/sidebar/sidebar-search/sidebar-search.js', ] }); }] } }) .state('dashboard.home', { url: '/home', templateUrl: 'app/views/dashboard/home.html', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/main.js', ] }); }] } }) .state('dashboard.flowV1', { templateUrl: 'app/views/flow_v1.html', url: '/flow/:app', controller: 'FlowControllerV1', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/flow_v1.js', ] }); }] } }) .state('dashboard.flow', { templateUrl: 'app/views/flow_v2.html', url: '/v2/flow/:app', controller: 'FlowControllerV2', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/flow_v2.js', ] }); }] } }) .state('dashboard.paramFlow', { templateUrl: 'app/views/param_flow.html', url: '/paramFlow/:app', controller: 'ParamFlowController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/param_flow.js', ] }); }] } }) .state('dashboard.clusterAppAssignManage', { templateUrl: 'app/views/cluster_app_assign_manage.html', url: '/cluster/assign_manage/:app', controller: 'SentinelClusterAppAssignManageController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/cluster_app_assign_manage.js', ] }); }] } }) .state('dashboard.clusterAppServerList', { templateUrl: 'app/views/cluster_app_server_list.html', url: '/cluster/server/:app', controller: 'SentinelClusterAppServerListController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/cluster_app_server_list.js', ] }); }] } }) .state('dashboard.clusterAppClientList', { templateUrl: 'app/views/cluster_app_client_list.html', url: '/cluster/client/:app', controller: 'SentinelClusterAppTokenClientListController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/cluster_app_token_client_list.js', ] }); }] } }) .state('dashboard.clusterSingle', { templateUrl: 'app/views/cluster_single_config.html', url: '/cluster/single/:app', controller: 'SentinelClusterSingleController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/cluster_single.js', ] }); }] } }) .state('dashboard.authority', { templateUrl: 'app/views/authority.html', url: '/authority/:app', controller: 'AuthorityRuleController', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/authority.js', ] }); }] } }) .state('dashboard.degrade', { templateUrl: 'app/views/degrade.html', url: '/degrade/:app', controller: 'DegradeCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/degrade.js', ] }); }] } }) .state('dashboard.system', { templateUrl: 'app/views/system.html', url: '/system/:app', controller: 'SystemCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/system.js', ] }); }] } }) .state('dashboard.machine', { templateUrl: 'app/views/machine.html', url: '/app/:app', controller: 'MachineCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/machine.js', ] }); }] } }) .state('dashboard.identity', { templateUrl: 'app/views/identity.html', url: '/identity/:app', controller: 'IdentityCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/identity.js', ] }); }] } }) .state('dashboard.gatewayIdentity', { templateUrl: 'app/views/gateway/identity.html', url: '/gateway/identity/:app', controller: 'GatewayIdentityCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/gateway/identity.js', ] }); }] } }) .state('dashboard.metric', { templateUrl: 'app/views/metric.html', url: '/metric/:app', controller: 'MetricCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/metric.js', ] }); }] } }) .state('dashboard.gatewayApi', { templateUrl: 'app/views/gateway/api.html', url: '/gateway/api/:app', controller: 'GatewayApiCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/gateway/api.js', ] }); }] } }) .state('dashboard.gatewayFlow', { templateUrl: 'app/views/gateway/flow.html', url: '/gateway/flow/:app', controller: 'GatewayFlowCtl', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/gateway/flow.js', ] }); }] } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/authority.js ================================================ /** * Authority rule controller. */ angular.module('sentinelDashboardApp').controller('AuthorityRuleController', ['$scope', '$stateParams', 'AuthorityRuleService', 'ngDialog', 'MachineService', function ($scope, $stateParams, AuthorityRuleService, ngDialog, MachineService) { $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; function getMachineRules() { if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); AuthorityRuleService.queryMachineRules($scope.app, mac[0], mac[1]) .success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; $scope.loadError = {message: data.msg}; } }) .error((data, header, config, status) => { $scope.loadError = {message: "未知错误"}; }); }; $scope.getMachineRules = getMachineRules; getMachineRules(); var authorityRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.authorityRuleDialog = { title: '编辑授权规则', type: 'edit', confirmBtnText: '保存', }; authorityRuleDialog = ngDialog.open({ template: '/app/views/dialog/authority-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { app: $scope.app, ip: mac[0], port: mac[1], rule: { strategy: 0, limitApp: '', } }; $scope.authorityRuleDialog = { title: '新增授权规则', type: 'add', confirmBtnText: '新增', showAdvanceButton: true, }; authorityRuleDialog = ngDialog.open({ template: '/app/views/dialog/authority-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if (!AuthorityRuleService.checkRuleValid($scope.currentRule.rule)) { return; } if ($scope.authorityRuleDialog.type === 'add') { addNewRuleAndPush($scope.currentRule); } else if ($scope.authorityRuleDialog.type === 'edit') { saveRuleAndPush($scope.currentRule, true); } }; function addNewRuleAndPush(rule) { AuthorityRuleService.addNewRule(rule).success((data) => { if (data.success) { getMachineRules(); authorityRuleDialog.close(); } else { alert('添加规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加规则失败:' + data.msg); } else { alert("添加规则失败:未知错误"); } }); } function saveRuleAndPush(rule, edit) { AuthorityRuleService.saveRule(rule).success(function (data) { if (data.success) { alert("修改规则成功"); getMachineRules(); if (edit) { authorityRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('修改规则失败:' + data.msg); } }).error((data) => { if (data) { alert('修改规则失败:' + data.msg); } else { alert("修改规则失败:未知错误"); } }); } function deleteRuleAndPush(entity) { if (entity.id === undefined || isNaN(entity.id)) { alert('规则 ID 不合法!'); return; } AuthorityRuleService.deleteRule(entity).success((data) => { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('删除规则失败:' + data.msg); } }).error((data) => { if (data) { alert('删除规则失败:' + data.msg); } else { alert("删除规则失败:未知错误"); } }); }; var confirmDialog; $scope.deleteRule = function (ruleEntity) { $scope.currentRule = ruleEntity; $scope.confirmDialog = { title: '删除授权规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下授权限流规则', attention: '资源名: ' + ruleEntity.rule.resource + ', 流控应用: ' + ruleEntity.rule.limitApp + ', 类型: ' + (ruleEntity.rule.strategy === 0 ? '白名单' : '黑名单'), confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type === 'delete_rule') { deleteRuleAndPush($scope.currentRule); } else { console.error('error'); } }; queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterAppAssignManageController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_CLIENT = 0; const CLUSTER_MODE_SERVER = 1; const DEFAULT_CLUSTER_SERVER_PORT = 18730; $scope.tmp = { curClientChosen: [], curRemainingClientChosen: [], curChosenServer: {}, }; function convertSetToString(set) { if (set === undefined) { return ''; } let s = ''; for (let i = 0; i < set.length; i++) { s = s + set[i]; if (i < set.length - 1) { s = s + ','; } } return s; } function convertStrToNamespaceSet(str) { if (str === undefined || str === '') { return []; } let arr = []; let spliced = str.split(','); spliced.forEach((v) => { arr.push(v.trim()); }); return arr; } function processAppSingleData(data) { if (data.state.server && data.state.server.namespaceSet) { data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); data.mode = data.state.stateInfo.mode; } } function removeFromArr(arr, v) { for (let i = 0; i < arr.length; i++) { if (arr[i] === v) { arr.splice(i, 1); break; } } } function resetChosen() { $scope.tmp.curClientChosen = []; $scope.tmp.curRemainingClientChosen = []; } function generateMachineId(e) { return e.ip + '@' + e.commandPort; } function applyClusterMap(appClusterMachineList) { if (!appClusterMachineList) { return; } let tmpMap = new Map(); $scope.clusterMap = []; $scope.remainingClientAddressList = []; let tmpServerList = []; let tmpClientList = []; appClusterMachineList.forEach((e) => { if (e.mode === CLUSTER_MODE_CLIENT) { tmpClientList.push(e); } else if (e.mode === CLUSTER_MODE_SERVER) { tmpServerList.push(e); } else { $scope.remainingClientAddressList.push(generateMachineId(e)); } }); tmpServerList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let group = { ip: ip, machineId: machineId, port: e.state.server.port, clientSet: [], namespaceSetStr: e.state.server.namespaceSetStr, belongToApp: true, }; if (!tmpMap.has(ip)) { tmpMap.set(ip, group); } }); tmpClientList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let targetServer = e.state.client.clientConfig.serverHost; let targetPort = e.state.client.clientConfig.serverPort; if (targetServer === undefined || targetServer === '' || targetPort === undefined || targetPort <= 0) { $scope.remainingClientAddressList.push(generateMachineId(e)); return; } if (!tmpMap.has(targetServer)) { let group = { ip: targetServer, machineId: targetServer, port: targetPort, clientSet: [machineId], belongToApp: false, }; tmpMap.set(targetServer, group); } else { let g = tmpMap.get(targetServer); g.clientSet.push(machineId); } }); tmpMap.forEach((v) => { if (v !== undefined) { $scope.clusterMap.push(v); } }); } $scope.onCurrentServerChange = () => { resetChosen(); }; $scope.remainingClientAddressList = []; $scope.moveToServerGroup = () => { let chosenServer = $scope.tmp.curChosenServer; if (!chosenServer || !chosenServer.machineId) { return; } $scope.tmp.curRemainingClientChosen.forEach(e => { chosenServer.clientSet.push(e); removeFromArr($scope.remainingClientAddressList, e); }); resetChosen(); }; $scope.moveToRemainingSharePool = () => { $scope.tmp.curClientChosen.forEach(e => { $scope.remainingClientAddressList.push(e); removeFromArr($scope.tmp.curChosenServer.clientSet, e); }); resetChosen(); }; function parseIpFromMachineId(machineId) { if (machineId.indexOf('@') === -1) { return machineId; } let arr = machineId.split('@'); return arr[0]; } $scope.addToServerList = () => { let group; $scope.tmp.curRemainingClientChosen.forEach(e => { group = { machineId: e, ip: parseIpFromMachineId(e), port: DEFAULT_CLUSTER_SERVER_PORT, clientSet: [], namespaceSetStr: 'default,' + $scope.app, belongToApp: true, }; $scope.clusterMap.push(group); removeFromArr($scope.remainingClientAddressList, e); $scope.tmp.curChosenServer = group; }); resetChosen(); }; $scope.removeFromServerList = () => { let chosenServer = $scope.tmp.curChosenServer; if (!chosenServer || !chosenServer.machineId) { return; } chosenServer.clientSet.forEach((e) => { if (e !== undefined) { $scope.remainingClientAddressList.push(e); } }); if (chosenServer.belongToApp || chosenServer.machineId.indexOf('@') !== -1) { $scope.remainingClientAddressList.push(chosenServer.machineId); } else { alert('提示:非本应用内机器将不会置回空闲列表中'); } removeFromArr($scope.clusterMap, chosenServer); resetChosen(); if ($scope.clusterMap.length > 0) { $scope.tmp.curChosenServer = $scope.clusterMap[0]; $scope.onCurrentServerChange(); } else { $scope.tmp.curChosenServer = {}; } }; function retrieveClusterAppInfo() { ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.appClusterMachineList = data.data; $scope.appClusterMachineList.forEach(processAppSingleData); applyClusterMap($scope.appClusterMachineList); if ($scope.clusterMap.length > 0) { $scope.tmp.curChosenServer = $scope.clusterMap[0]; $scope.onCurrentServerChange(); } } else { $scope.appClusterMachineList = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); } retrieveClusterAppInfo(); $scope.saveAndApplyAssign = () => { let ok = confirm('是否确认执行变更?'); if (!ok) { return; } let cm = $scope.clusterMap; if (!cm) { cm = []; } cm.forEach((e) => { e.namespaceSet = convertStrToNamespaceSet(e.namespaceSetStr); }); cm.namespaceSet = convertStrToNamespaceSet(cm.namespaceSetStr); let request = { clusterMap: cm, remainingList: $scope.remainingClientAddressList, }; ClusterStateService.applyClusterFullAssignOfApp($scope.app, request).success((data) => { if (data.code === 0 && data.data) { let failedServerSet = data.data.failedServerSet; let failedClientSet = data.data.failedClientSet; if (failedClientSet.length === 0 && failedServerSet.length === 0) { alert('全部推送成功'); } else { alert('推送完毕。token server 失败列表:' + JSON.stringify(failedServerSet) + '; token client 失败列表:' + JSON.stringify(failedClientSet)); } retrieveClusterAppInfo(); } else { if (data.code === UNSUPPORTED_CODE) { alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('推送失败:' + data.msg); } } }).error(() => { alert('未知错误'); }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterAppServerListController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_CLIENT = 0; const CLUSTER_MODE_SERVER = 1; const DEFAULT_CLUSTER_SERVER_PORT = 18730; const DEFAULT_NAMESPACE = 'default'; const DEFAULT_MAX_ALLOWED_QPS = 20000; // tmp for dialog temporary data. $scope.tmp = { curClientChosen: [], curRemainingClientChosen: [], curChosenServer: {}, }; $scope.remainingMachineList = []; function convertSetToString(set) { if (set === undefined) { return ''; } if (set.length === 1 && set[0] === DEFAULT_NAMESPACE) { return DEFAULT_NAMESPACE; } let s = ''; for (let i = 0; i < set.length; i++) { let ns = set[i]; if (ns !== DEFAULT_NAMESPACE) { s = s + ns; if (i < set.length - 1) { s = s + ','; } } } return s; } function convertStrToNamespaceSet(str) { if (str === undefined || str === '') { return []; } let arr = []; let spliced = str.split(','); spliced.forEach((v) => { arr.push(v.trim()); }); return arr; } function processAppSingleData(data) { if (data.state.server && data.state.server.namespaceSet) { data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); data.mode = data.state.stateInfo.mode; } } function removeFromArr(arr, v) { for (let i = 0; i < arr.length; i++) { if (arr[i] === v) { arr.splice(i, 1); break; } } } function removeFromArrIf(arr, f) { for (let i = 0; i < arr.length; i++) { if (f(arr[i]) === true) { arr.splice(i, 1); break; } } } function resetAssignDialogChosen() { $scope.tmp.curClientChosen = []; $scope.tmp.curRemainingClientChosen = []; } function generateMachineId(e) { return e.ip + '@' + e.commandPort; } function applyClusterMap(appClusterMachineList) { if (!appClusterMachineList) { return; } let tmpMap = new Map(); let serverCommandPortMap = new Map(); $scope.clusterMap = []; $scope.remainingMachineList = []; let tmpServerList = []; let tmpClientList = []; appClusterMachineList.forEach((e) => { if (e.mode === CLUSTER_MODE_CLIENT) { tmpClientList.push(e); } else if (e.mode === CLUSTER_MODE_SERVER) { tmpServerList.push(e); } else { $scope.remainingMachineList.push(generateMachineId(e)); } }); tmpServerList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let group = { ip: ip, machineId: machineId, port: e.state.server.port, clientSet: [], namespaceSetStr: e.state.server.namespaceSetStr, maxAllowedQps: e.state.server.flow.maxAllowedQps, belongToApp: true, }; if (!tmpMap.has(machineId)) { tmpMap.set(machineId, group); } serverCommandPortMap.set(ip + ':' + e.state.server.port, e.commandPort); }); tmpClientList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let targetServer = e.state.client.clientConfig.serverHost; let targetPort = e.state.client.clientConfig.serverPort; if (targetServer === undefined || targetServer === '' || targetPort === undefined || targetPort <= 0) { $scope.remainingMachineList.push(generateMachineId(e)); return; } let serverHostPort = targetServer + ':' + targetPort; if (serverCommandPortMap.has(serverHostPort)) { let serverCommandPort = serverCommandPortMap.get(serverHostPort); let g; if (serverCommandPort < 0) { // Not belong to this app. g = tmpMap.get(serverHostPort); } else { // Belong to this app. g = tmpMap.get(targetServer + '@' + serverCommandPort); } g.clientSet.push(machineId); } else { let group = { ip: targetServer, machineId: serverHostPort, port: targetPort, clientSet: [machineId], belongToApp: false, }; tmpMap.set(serverHostPort, group); // Indicates that it's not belonging to current app. serverCommandPortMap.set(serverHostPort, -1); } // if (!tmpMap.has(serverHostPort)) { // let group = { // ip: targetServer, // machineId: targetServer, // port: targetPort, // clientSet: [machineId], // belongToApp: false, // }; // tmpMap.set(targetServer, group); // } else { // let g = tmpMap.get(targetServer); // g.clientSet.push(machineId); // } }); tmpMap.forEach((v) => { if (v !== undefined) { $scope.clusterMap.push(v); } }); } $scope.notChosenServer = (id) => { return id !== $scope.serverAssignDialogData.serverData.currentServer; }; $scope.onCurrentServerChange = () => { resetAssignDialogChosen(); }; $scope.moveToServerGroup = () => { $scope.tmp.curRemainingClientChosen.forEach(e => { $scope.serverAssignDialogData.serverData.clientSet.push(e); removeFromArr($scope.remainingMachineList, e); }); resetAssignDialogChosen(); }; $scope.moveToRemainingSharePool = () => { $scope.tmp.curClientChosen.forEach(e => { $scope.remainingMachineList.push(e); removeFromArr($scope.serverAssignDialogData.serverData.clientSet, e); }); resetAssignDialogChosen(); }; function parseIpFromMachineId(machineId) { if (machineId.indexOf(':') !== -1) { return machineId.split(':')[0]; } if (machineId.indexOf('@') === -1) { return machineId; } let arr = machineId.split('@'); return arr[0]; } function retrieveClusterAssignInfoOfApp() { ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.appClusterMachineList = data.data; $scope.appClusterMachineList.forEach(processAppSingleData); applyClusterMap($scope.appClusterMachineList); } else { $scope.appClusterMachineList = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); } $scope.newServerDialog = () => { retrieveClusterAssignInfoOfApp(); $scope.serverAssignDialogData = { title: '新增 Token Server', type: 'add', confirmBtnText: '保存', serverData: { serverType: 0, clientSet: [], serverPort: DEFAULT_CLUSTER_SERVER_PORT, maxAllowedQps: DEFAULT_MAX_ALLOWED_QPS, } }; $scope.serverAssignDialog = ngDialog.open({ template: '/app/views/dialog/cluster/cluster-server-assign-dialog.html', width: 1000, overlay: true, scope: $scope }); }; $scope.modifyServerAssignConfig = (serverVO) => { let id = serverVO.id; ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.appClusterMachineList = data.data; $scope.appClusterMachineList.forEach(processAppSingleData); applyClusterMap($scope.appClusterMachineList); let clusterMap = $scope.clusterMap; let d; for (let i = 0; i < clusterMap.length; i++) { if (clusterMap[i].machineId === id) { d = clusterMap[i]; } } if (!d) { alert('状态错误'); return; } $scope.serverAssignDialogData = { title: 'Token Server 分配编辑', type: 'edit', confirmBtnText: '保存', serverData: { currentServer: d.machineId, belongToApp: serverVO.belongToApp, serverPort: d.port, clientSet: d.clientSet, } }; if (d.maxAllowedQps !== undefined) { $scope.serverAssignDialogData.serverData.maxAllowedQps = d.maxAllowedQps; } $scope.serverAssignDialog = ngDialog.open({ template: '/app/views/dialog/cluster/cluster-server-assign-dialog.html', width: 1000, overlay: true, scope: $scope }); } else { if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); }; function getRemainingMachineList() { return $scope.remainingMachineList.filter((e) => $scope.notChosenServer(e)); } function doApplyNewSingleServerAssign() { let ok = confirm('是否确认执行变更?'); if (!ok) { return; } let serverData = $scope.serverAssignDialogData.serverData; let belongToApp = serverData.serverType == 0; // don't modify here! let machineId = serverData.currentServer; let request = { clusterMap: { machineId: machineId, ip: parseIpFromMachineId(machineId), port: serverData.serverPort, clientSet: serverData.clientSet, belongToApp: belongToApp, maxAllowedQps: serverData.maxAllowedQps, }, remainingList: getRemainingMachineList(), }; ClusterStateService.applyClusterSingleServerAssignOfApp($scope.app, request).success((data) => { if (data.code === 0 && data.data) { let failedServerSet = data.data.failedServerSet; let failedClientSet = data.data.failedClientSet; if (failedClientSet.length === 0 && failedServerSet.length === 0) { alert('全部推送成功'); } else { let failedSet = []; if (failedServerSet) { failedServerSet.forEach((e) => { failedSet.push(e); }); } if (failedClientSet) { failedClientSet.forEach((e) => { failedSet.push(e); }); } alert('推送完毕。失败机器列表:' + JSON.stringify(failedSet)); } location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('推送失败:' + data.msg); } } }).error(() => { alert('未知错误'); }); } function doApplySingleServerAssignEdit() { let ok = confirm('是否确认执行变更?'); if (!ok) { return; } let serverData = $scope.serverAssignDialogData.serverData; let machineId = serverData.currentServer; let request = { clusterMap: { machineId: machineId, ip: parseIpFromMachineId(machineId), port: serverData.serverPort, clientSet: serverData.clientSet, belongToApp: serverData.belongToApp, }, remainingList: $scope.remainingMachineList, }; if (serverData.maxAllowedQps !== undefined) { request.clusterMap.maxAllowedQps = serverData.maxAllowedQps; } ClusterStateService.applyClusterSingleServerAssignOfApp($scope.app, request).success((data) => { if (data.code === 0 && data.data) { let failedServerSet = data.data.failedServerSet; let failedClientSet = data.data.failedClientSet; if (failedClientSet.length === 0 && failedServerSet.length === 0) { alert('全部推送成功'); } else { let failedSet = []; failedServerSet.forEach(failedSet.push); failedClientSet.forEach(failedSet.push); alert('推送完毕。失败机器列表:' + JSON.stringify(failedSet)); } location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('推送失败:' + data.msg); } } }).error(() => { alert('未知错误'); }); } $scope.saveAssignForDialog = () => { if (!checkAssignDialogValid()) { return; } if ($scope.serverAssignDialogData.type === 'add') { doApplyNewSingleServerAssign(); } else if ($scope.serverAssignDialogData.type === 'edit') { doApplySingleServerAssignEdit(); } else { alert('未知的操作'); } }; function checkAssignDialogValid() { let serverData = $scope.serverAssignDialogData.serverData; if (serverData.currentServer === undefined || serverData.currentServer === '') { alert('请指定有效的 Token Server'); return false; } if (serverData.serverPort === undefined || serverData.serverPort <= 0 || serverData.serverPort > 65535) { alert('请输入合法的端口值'); return false; } if (serverData.maxAllowedQps !== undefined && serverData.maxAllowedQps < 0) { alert('请输入合法的最大允许 QPS'); return false; } return true; } $scope.viewConnectionDetail = (serverVO) => { $scope.connectionDetailDialogData = { serverData: serverVO }; $scope.connectionDetailDialog = ngDialog.open({ template: '/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html', width: 700, overlay: true, scope: $scope }); }; function generateRequestLimitDataStr(limitData) { if (limitData.length === 1 && limitData[0].namespace === DEFAULT_NAMESPACE) { return 'default: ' + limitData[0].currentQps + ' / ' + limitData[0].maxAllowedQps; } for (let i = 0; i < limitData.length; i++) { let crl = limitData[i]; if (crl.namespace === $scope.app) { return '' + crl.currentQps + ' / ' + crl.maxAllowedQps; } } return '0'; } function processServerListData(serverVO) { if (serverVO.state && serverVO.state.namespaceSet) { serverVO.state.namespaceSetStr = convertSetToString(serverVO.state.namespaceSet); } if (serverVO.state && serverVO.state.requestLimitData) { serverVO.state.requestLimitDataStr = generateRequestLimitDataStr(serverVO.state.requestLimitData); } } $scope.generateConnectionSet = (data) => { let connectionSet = data; let s = ''; if (connectionSet) { s = s + '['; for (let i = 0; i < connectionSet.length; i++) { s = s + connectionSet[i].address; if (i < connectionSet.length - 1) { s = s + ', '; } } s = s + ']'; } else { s = '[]'; } return s; }; function retrieveClusterServerInfo() { ClusterStateService.fetchClusterServerStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.serverVOList = data.data; $scope.serverVOList.forEach(processServerListData); } else { $scope.serverVOList = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); } retrieveClusterServerInfo(); let confirmUnbindServerDialog; $scope.unbindServer = (id) => { $scope.pendingUnbindIds = [id]; $scope.confirmDialog = { title: '移除 Token Server', type: 'unbind_token_server', attentionTitle: '请确认是否移除以下 Token Server(该 server 下的 client 也会解除分配)', attention: id + '', confirmBtnText: '移除', }; confirmUnbindServerDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; function apiUnbindServerAssign(ids) { ClusterStateService.applyClusterServerBatchUnbind($scope.app, ids).success((data) => { if (data.code === 0 && data.data) { let failedServerSet = data.data.failedServerSet; let failedClientSet = data.data.failedClientSet; if (failedClientSet.length === 0 && failedServerSet.length === 0) { alert('成功'); } else { alert('操作推送完毕,部分失败机器列表:' + JSON.stringify(failedClientSet)); } location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('推送失败:' + data.msg); } } }).error(() => { alert('未知错误'); }); // confirmUnbindServerDialog.close(); } // Confirm function for confirm dialog. $scope.confirm = () => { if ($scope.confirmDialog.type === 'unbind_token_server') { apiUnbindServerAssign($scope.pendingUnbindIds); } else { console.error('Error dialog when unbinding token server'); } }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterAppAssignManageController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_CLIENT = 0; const CLUSTER_MODE_SERVER = 1; const DEFAULT_CLUSTER_SERVER_PORT = 18730; $scope.tmp = { curClientChosen: [], curRemainingClientChosen: [], curChosenServer: {}, }; function convertSetToString(set) { if (set === undefined) { return ''; } let s = ''; for (let i = 0; i < set.length; i++) { s = s + set[i]; if (i < set.length - 1) { s = s + ','; } } return s; } function convertStrToNamespaceSet(str) { if (str === undefined || str === '') { return []; } let arr = []; let spliced = str.split(','); spliced.forEach((v) => { arr.push(v.trim()); }); return arr; } function processAppSingleData(data) { if (data.state.server && data.state.server.namespaceSet) { data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); data.mode = data.state.stateInfo.mode; } } function removeFromArr(arr, v) { for (let i = 0; i < arr.length; i++) { if (arr[i] === v) { arr.splice(i, 1); break; } } } function resetChosen() { $scope.tmp.curClientChosen = []; $scope.tmp.curRemainingClientChosen = []; } function generateMachineId(e) { return e.ip + '@' + e.commandPort; } function applyClusterMap(appClusterMachineList) { if (!appClusterMachineList) { return; } let tmpMap = new Map(); $scope.clusterMap = []; $scope.remainingClientAddressList = []; let tmpServerList = []; let tmpClientList = []; appClusterMachineList.forEach((e) => { if (e.mode === CLUSTER_MODE_CLIENT) { tmpClientList.push(e); } else if (e.mode === CLUSTER_MODE_SERVER) { tmpServerList.push(e); } else { $scope.remainingClientAddressList.push(generateMachineId(e)); } }); tmpServerList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let group = { ip: ip, machineId: machineId, port: e.state.server.port, clientSet: [], namespaceSetStr: e.state.server.namespaceSetStr, belongToApp: true, }; if (!tmpMap.has(ip)) { tmpMap.set(ip, group); } }); tmpClientList.forEach((e) => { let ip = e.ip; let machineId = ip + '@' + e.commandPort; let targetServer = e.state.client.clientConfig.serverHost; let targetPort = e.state.client.clientConfig.serverPort; if (targetServer === undefined || targetServer === '' || targetPort === undefined || targetPort <= 0) { $scope.remainingClientAddressList.push(generateMachineId(e)); return; } if (!tmpMap.has(targetServer)) { let group = { ip: targetServer, machineId: targetServer, port: targetPort, clientSet: [machineId], belongToApp: false, }; tmpMap.set(targetServer, group); } else { let g = tmpMap.get(targetServer); g.clientSet.push(machineId); } }); tmpMap.forEach((v) => { if (v !== undefined) { $scope.clusterMap.push(v); } }); } $scope.onCurrentServerChange = () => { resetChosen(); }; $scope.remainingClientAddressList = []; $scope.moveToServerGroup = () => { let chosenServer = $scope.tmp.curChosenServer; if (!chosenServer || !chosenServer.machineId) { return; } $scope.tmp.curRemainingClientChosen.forEach(e => { chosenServer.clientSet.push(e); removeFromArr($scope.remainingClientAddressList, e); }); resetChosen(); }; $scope.moveToRemainingSharePool = () => { $scope.tmp.curClientChosen.forEach(e => { $scope.remainingClientAddressList.push(e); removeFromArr($scope.tmp.curChosenServer.clientSet, e); }); resetChosen(); }; function parseIpFromMachineId(machineId) { if (machineId.indexOf('@') === -1) { return machineId; } let arr = machineId.split('@'); return arr[0]; } $scope.addToServerList = () => { let group; $scope.tmp.curRemainingClientChosen.forEach(e => { group = { machineId: e, ip: parseIpFromMachineId(e), port: DEFAULT_CLUSTER_SERVER_PORT, clientSet: [], namespaceSetStr: 'default,' + $scope.app, belongToApp: true, }; $scope.clusterMap.push(group); removeFromArr($scope.remainingClientAddressList, e); $scope.tmp.curChosenServer = group; }); resetChosen(); }; $scope.removeFromServerList = () => { let chosenServer = $scope.tmp.curChosenServer; if (!chosenServer || !chosenServer.machineId) { return; } chosenServer.clientSet.forEach((e) => { if (e !== undefined) { $scope.remainingClientAddressList.push(e); } }); if (chosenServer.belongToApp || chosenServer.machineId.indexOf('@') !== -1) { $scope.remainingClientAddressList.push(chosenServer.machineId); } else { alert('提示:非本应用内机器将不会置回空闲列表中'); } removeFromArr($scope.clusterMap, chosenServer); resetChosen(); if ($scope.clusterMap.length > 0) { $scope.tmp.curChosenServer = $scope.clusterMap[0]; $scope.onCurrentServerChange(); } else { $scope.tmp.curChosenServer = {}; } }; function retrieveClusterAppInfo() { ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.appClusterMachineList = data.data; $scope.appClusterMachineList.forEach(processAppSingleData); applyClusterMap($scope.appClusterMachineList); if ($scope.clusterMap.length > 0) { $scope.tmp.curChosenServer = $scope.clusterMap[0]; $scope.onCurrentServerChange(); } } else { $scope.appClusterMachineList = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); } retrieveClusterAppInfo(); $scope.saveAndApplyAssign = () => { let ok = confirm('是否确认执行变更?'); if (!ok) { return; } let cm = $scope.clusterMap; if (!cm) { cm = []; } cm.forEach((e) => { e.namespaceSet = convertStrToNamespaceSet(e.namespaceSetStr); }); cm.namespaceSet = convertStrToNamespaceSet(cm.namespaceSetStr); let request = { clusterMap: cm, remainingList: $scope.remainingClientAddressList, }; ClusterStateService.applyClusterFullAssignOfApp($scope.app, request).success((data) => { if (data.code === 0 && data.data) { let failedServerSet = data.data.failedServerSet; let failedClientSet = data.data.failedClientSet; if (failedClientSet.length === 0 && failedServerSet.length === 0) { alert('全部推送成功'); } else { alert('推送完毕。token server 失败列表:' + JSON.stringify(failedServerSet) + '; token client 失败列表:' + JSON.stringify(failedClientSet)); } retrieveClusterAppInfo(); } else { if (data.code === UNSUPPORTED_CODE) { alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('推送失败:' + data.msg); } } }).error(() => { alert('未知错误'); }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterAppServerMonitorController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_SERVER = 1; $scope.tmp = { curChosenServer: {}, }; function convertSetToString(set) { if (set === undefined) { return ''; } let s = ''; for (let i = 0; i < set.length; i++) { s = s + set[i]; if (i < set.length - 1) { s = s + ','; } } return s; } function processServerData(serverVO) { if (serverVO.state && serverVO.state.namespaceSet) { serverVO.state.namespaceSetStr = convertSetToString(serverVO.state.namespaceSet); } } $scope.generateConnectionSet = (data) => { let connectionSet = data; let s = ''; if (connectionSet) { s = s + '['; for (let i = 0; i < connectionSet.length; i++) { s = s + connectionSet[i].address; if (i < connectionSet.length - 1) { s = s + ', '; } } s = s + ']'; } else { s = '[]'; } return s; }; $scope.onChosenServerChange = () => { }; function retrieveClusterServerInfo() { ClusterStateService.fetchClusterServerStateOfApp($scope.app).success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.serverVOList = data.data; $scope.serverVOList.forEach(processServerData); if ($scope.serverVOList.length > 0) { $scope.tmp.curChosenServer = $scope.serverVOList[0]; $scope.onChosenServerChange(); } } else { $scope.serverVOList = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error(() => { $scope.loadError = {message: '未知错误'}; }); } retrieveClusterServerInfo(); $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterAppTokenClientListController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_CLIENT = 0; const CLUSTER_MODE_SERVER = 1; function processClientData(clientVO) { } $scope.modifyClientConfigDialog = (clientVO) => { if (!clientVO) { return; } $scope.ccDialogData = { ip: clientVO.ip, commandPort: clientVO.commandPort, clientId: clientVO.id, serverHost: clientVO.state.clientConfig.serverHost, serverPort: clientVO.state.clientConfig.serverPort, requestTimeout: clientVO.state.clientConfig.requestTimeout, }; $scope.ccDialog = ngDialog.open({ template: '/app/views/dialog/cluster/cluster-client-config-dialog.html', width: 700, overlay: true, scope: $scope }); }; function checkValidClientConfig(config) { if (!config.serverHost || config.serverHost.trim() == '') { alert('请输入有效的 Token Server IP'); return false; } if (config.serverPort === undefined || config.serverPort <= 0 || config.serverPort > 65535) { alert('请输入有效的 Token Server 端口'); return false; } if (config.requestTimeout === undefined || config.requestTimeout <= 0) { alert('请输入有效的请求超时时长'); return false; } return true; } $scope.doModifyClientConfig = () => { if (!checkValidClientConfig($scope.ccDialogData)) { return; } let id = $scope.ccDialogData.id; let request = { app: $scope.app, ip: $scope.ccDialogData.ip, port: $scope.ccDialogData.commandPort, mode: CLUSTER_MODE_CLIENT, clientConfig: { serverHost: $scope.ccDialogData.serverHost, serverPort: $scope.ccDialogData.serverPort, requestTimeout: $scope.ccDialogData.requestTimeout, } }; ClusterStateService.modifyClusterConfig(request).success((data) => { if (data.code === 0 && data.data) { alert('修改 Token Client 配置成功'); window.location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('机器 ' + id + ' 的 Sentinel 没有引入集群限流客户端,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('修改失败:' + data.msg); } } }).error((data, header, config, status) => { alert('未知错误'); }); }; function retrieveClusterTokenClientInfo() { ClusterStateService.fetchClusterClientStateOfApp($scope.app) .success((data) => { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.clientVOList = data.data; $scope.clientVOList.forEach(processClientData); } else { $scope.clientVOList = []; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }) .error(() => { $scope.loadError = {message: '未知错误'}; }); } retrieveClusterTokenClientInfo(); $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_single.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SentinelClusterSingleController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; const UNSUPPORTED_CODE = 4041; const CLUSTER_MODE_CLIENT = 0; const CLUSTER_MODE_SERVER = 1; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; function convertSetToString(set) { if (set === undefined) { return ''; } let s = ''; for (let i = 0; i < set.length; i++) { s = s + set[i]; if (i < set.length - 1) { s = s + ','; } } return s; } function convertStrToNamespaceSet(str) { if (str === undefined || str === '') { return []; } let arr = []; let spliced = str.split(','); spliced.forEach((v) => { arr.push(v.trim()); }); return arr; } function fetchMachineClusterState() { if (!$scope.macInputModel || $scope.macInputModel === '') { return; } let mac = $scope.macInputModel.split(':'); ClusterStateService.fetchClusterUniversalStateSingle($scope.app, mac[0], mac[1]).success(function (data) { if (data.code == 0 && data.data) { $scope.loadError = undefined; $scope.stateVO = data.data; $scope.stateVO.currentMode = $scope.stateVO.stateInfo.mode; if ($scope.stateVO.server && $scope.stateVO.server.namespaceSet) { $scope.stateVO.server.namespaceSetStr = convertSetToString($scope.stateVO.server.namespaceSet); } } else { $scope.stateVO = {}; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: '机器 ' + mac[0] + ':' + mac[1] + ' 的 Sentinel 客户端版本不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} } else { $scope.loadError = {message: data.msg}; } } }).error((data, header, config, status) => { $scope.loadError = {message: '未知错误'}; }); } fetchMachineClusterState(); function checkValidClientConfig(stateVO) { if (!stateVO.client || !stateVO.client.clientConfig) { alert('不合法的配置'); return false; } let config = stateVO.client.clientConfig; if (!config.serverHost || config.serverHost.trim() == '') { alert('请输入有效的 Token Server IP'); return false; } if (config.serverPort === undefined || config.serverPort <= 0 || config.serverPort > 65535) { alert('请输入有效的 Token Server 端口'); return false; } if (config.requestTimeout === undefined || config.requestTimeout <= 0) { alert('请输入有效的请求超时时长'); return false; } return true; } function sendClusterClientRequest(stateVO) { if (!checkValidClientConfig(stateVO)) { return; } if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); let request = { app: $scope.app, ip: mac[0], port: mac[1], }; request.mode = CLUSTER_MODE_CLIENT; request.clientConfig = stateVO.client.clientConfig; ClusterStateService.modifyClusterConfig(request).success(function (data) { if (data.code == 0 && data.data) { alert('修改集群限流客户端配置成功'); window.location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('机器 ' + mac[0] + ':' + mac[1] + ' 的 Sentinel 客户端版本不支持集群限流客户端,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('修改失败:' + data.msg); } } }).error((data, header, config, status) => { alert('未知错误'); }); } function checkValidServerConfig(stateVO) { if (!stateVO.server || !stateVO.server.transport) { alert('不合法的配置'); return false; } if (stateVO.server.namespaceSetStr === undefined || stateVO.server.namespaceSetStr == '') { alert('请输入有效的命名空间集合(多个 namespace 以 , 分隔)'); return false; } let transportConfig = stateVO.server.transport; if (transportConfig.port === undefined || transportConfig.port <= 0 || transportConfig.port > 65535) { alert('请输入有效的 Token Server 端口'); return false; } let flowConfig = stateVO.server.flow; if (flowConfig.maxAllowedQps === undefined || flowConfig.maxAllowedQps < 0) { alert('请输入有效的最大允许 QPS'); return false; } // if (transportConfig.idleSeconds === undefined || transportConfig.idleSeconds <= 0) { // alert('请输入有效的连接清理时长 (idleSeconds)'); // return false; // } return true; } function sendClusterServerRequest(stateVO) { if (!checkValidServerConfig(stateVO)) { return; } if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); let request = { app: $scope.app, ip: mac[0], port: mac[1], }; request.mode = CLUSTER_MODE_SERVER; request.flowConfig = stateVO.server.flow; request.transportConfig = stateVO.server.transport; request.namespaceSet = convertStrToNamespaceSet(stateVO.server.namespaceSetStr); ClusterStateService.modifyClusterConfig(request).success(function (data) { if (data.code == 0 && data.data) { alert('修改集群限流服务端配置成功'); window.location.reload(); } else { if (data.code === UNSUPPORTED_CODE) { alert('机器 ' + mac[0] + ':' + mac[1] + ' 的 Sentinel 客户端版本不支持集群限流服务端,请升级至 1.4.0 以上版本并引入相关依赖。'); } else { alert('修改失败:' + data.msg); } } }).error((data, header, config, status) => { alert('未知错误'); }); } $scope.saveConfig = () => { let ok = confirm('是否确定修改集群限流配置?'); if (!ok) { return; } let mode = $scope.stateVO.stateInfo.mode; if (mode != 1 && mode != 0) { alert('未知的集群限流模式'); return; } if (mode == 0) { sendClusterClientRequest($scope.stateVO); } else { sendClusterServerRequest($scope.stateVO); } }; function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code === 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptionsOrigin = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptionsOrigin.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); $scope.macsInputOptions = $scope.macsInputOptionsOrigin; } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); } queryAppMachines(); $scope.$watch('searchKey', function () { if (!$scope.macsInputOptions) { return; } if ($scope.searchKey) { $scope.macsInputOptions = $scope.macsInputOptionsOrigin .filter((e) => e.value.indexOf($scope.searchKey) !== -1); } else { $scope.macsInputOptions = $scope.macsInputOptionsOrigin; } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } else { $scope.macInputModel = ''; } }); $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { fetchMachineClusterState(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/degrade.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDialog', 'MachineService', function ($scope, $stateParams, DegradeService, ngDialog, MachineService) { //初始化 $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); DegradeService.queryMachineRules($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; } }); }; $scope.getMachineRules = getMachineRules; var degradeRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.degradeRuleDialog = { title: '编辑熔断规则', type: 'edit', confirmBtnText: '保存' }; degradeRuleDialog = ngDialog.open({ template: '/app/views/dialog/degrade-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { grade: 0, app: $scope.app, ip: mac[0], port: mac[1], limitApp: 'default', minRequestAmount: 5, statIntervalMs: 1000, }; $scope.degradeRuleDialog = { title: '新增熔断规则', type: 'add', confirmBtnText: '新增' }; degradeRuleDialog = ngDialog.open({ template: '/app/views/dialog/degrade-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if (!DegradeService.checkRuleValid($scope.currentRule)) { return; } if ($scope.degradeRuleDialog.type === 'add') { addNewRule($scope.currentRule); } else if ($scope.degradeRuleDialog.type === 'edit') { saveRule($scope.currentRule, true); } }; function parseDegradeMode(grade) { switch (grade) { case 0: return '慢调用比例'; case 1: return '异常比例'; case 2: return '异常数'; default: return '未知'; } } var confirmDialog; $scope.deleteRule = function (rule) { $scope.currentRule = rule; $scope.confirmDialog = { title: '删除熔断规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下熔断规则', attention: '资源名: ' + rule.resource + ', 熔断策略: ' + parseDegradeMode(rule.grade) + ', 阈值: ' + rule.count, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type == 'delete_rule') { deleteRule($scope.currentRule); } else { console.error('error'); } }; function deleteRule(rule) { DegradeService.deleteRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('失败:' + data.msg); } }); }; function addNewRule(rule) { DegradeService.newRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); degradeRuleDialog.close(); } else { alert('失败:' + data.msg); } }); }; function saveRule(rule, edit) { DegradeService.saveRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); if (edit) { degradeRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('失败:' + data.msg); } }); } queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code === 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v1.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('FlowControllerV1', ['$scope', '$stateParams', 'FlowServiceV1', 'ngDialog', 'MachineService', function ($scope, $stateParams, FlowService, ngDialog, MachineService) { $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; $scope.generateThresholdTypeShow = (rule) => { if (!rule.clusterMode) { return '单机'; } if (rule.clusterConfig.thresholdType === 0) { return '集群均摊'; } else if (rule.clusterConfig.thresholdType === 1) { return '集群总体'; } else { return '集群'; } }; getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); FlowService.queryMachineRules($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; } }); }; $scope.getMachineRules = getMachineRules; var flowRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.flowRuleDialog = { title: '编辑流控规则', type: 'edit', confirmBtnText: '保存', showAdvanceButton: rule.controlBehavior == 0 && rule.strategy == 0 }; flowRuleDialog = ngDialog.open({ template: '/app/views/dialog/flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { grade: 1, strategy: 0, controlBehavior: 0, app: $scope.app, ip: mac[0], port: mac[1], limitApp: 'default', clusterMode: false, clusterConfig: { thresholdType: 0 } }; $scope.flowRuleDialog = { title: '新增流控规则', type: 'add', confirmBtnText: '新增', showAdvanceButton: true, }; flowRuleDialog = ngDialog.open({ template: '/app/views/dialog/flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if (!FlowService.checkRuleValid($scope.currentRule)) { return; } if ($scope.flowRuleDialog.type === 'add') { addNewRule($scope.currentRule); } else if ($scope.flowRuleDialog.type === 'edit') { saveRule($scope.currentRule, true); } }; var confirmDialog; $scope.deleteRule = function (rule) { $scope.currentRule = rule; $scope.confirmDialog = { title: '删除流控规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下流控规则', attention: '资源名: ' + rule.resource + ', 流控应用: ' + rule.limitApp + ', 阈值类型: ' + (rule.grade == 0 ? '线程数' : 'QPS') + ', 阈值: ' + rule.count, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type === 'delete_rule') { deleteRule($scope.currentRule); } else { console.error('error'); } }; function deleteRule(rule) { FlowService.deleteRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('失败:' + data.msg); } }); }; function addNewRule(rule) { FlowService.newRule(rule).success(function (data) { if (data.code === 0) { getMachineRules(); flowRuleDialog.close(); } else { alert('失败:' + data.msg); } }); }; $scope.onOpenAdvanceClick = function () { $scope.flowRuleDialog.showAdvanceButton = false; }; $scope.onCloseAdvanceClick = function () { $scope.flowRuleDialog.showAdvanceButton = true; }; function saveRule(rule, edit) { FlowService.saveRule(rule).success(function (data) { if (data.code === 0) { getMachineRules(); if (edit) { flowRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('失败:' + data.msg); } }); } queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v2.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('FlowControllerV2', ['$scope', '$stateParams', 'FlowServiceV2', 'ngDialog', 'MachineService', function ($scope, $stateParams, FlowService, ngDialog, MachineService) { $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; $scope.generateThresholdTypeShow = (rule) => { if (!rule.clusterMode) { return '单机'; } if (rule.clusterConfig.thresholdType === 0) { return '集群均摊'; } else if (rule.clusterConfig.thresholdType === 1) { return '集群总体'; } else { return '集群'; } }; getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); FlowService.queryMachineRules($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; } }); }; $scope.getMachineRules = getMachineRules; var flowRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.flowRuleDialog = { title: '编辑流控规则', type: 'edit', confirmBtnText: '保存', showAdvanceButton: rule.controlBehavior == 0 && rule.strategy == 0 }; flowRuleDialog = ngDialog.open({ template: '/app/views/dialog/flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { grade: 1, strategy: 0, controlBehavior: 0, app: $scope.app, ip: mac[0], port: mac[1], limitApp: 'default', clusterMode: false, clusterConfig: { thresholdType: 0, fallbackToLocalWhenFail: true } }; $scope.flowRuleDialog = { title: '新增流控规则', type: 'add', confirmBtnText: '新增', showAdvanceButton: true, }; flowRuleDialog = ngDialog.open({ template: '/app/views/dialog/flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if (!FlowService.checkRuleValid($scope.currentRule)) { return; } if ($scope.flowRuleDialog.type === 'add') { addNewRule($scope.currentRule); } else if ($scope.flowRuleDialog.type === 'edit') { saveRule($scope.currentRule, true); } }; var confirmDialog; $scope.deleteRule = function (rule) { $scope.currentRule = rule; $scope.confirmDialog = { title: '删除流控规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下流控规则', attention: '资源名: ' + rule.resource + ', 流控应用: ' + rule.limitApp + ', 阈值类型: ' + (rule.grade == 0 ? '线程数' : 'QPS') + ', 阈值: ' + rule.count, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type === 'delete_rule') { deleteRule($scope.currentRule); } else { console.error('error'); } }; function deleteRule(rule) { FlowService.deleteRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('失败!'); } }); }; function addNewRule(rule) { FlowService.newRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); flowRuleDialog.close(); } else { alert('失败!'); } }); }; $scope.onOpenAdvanceClick = function () { $scope.flowRuleDialog.showAdvanceButton = false; }; $scope.onCloseAdvanceClick = function () { $scope.flowRuleDialog.showAdvanceButton = true; }; function saveRule(rule, edit) { FlowService.saveRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); if (edit) { flowRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('失败!'); } }); } queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/api.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('GatewayApiCtl', ['$scope', '$stateParams', 'GatewayApiService', 'ngDialog', 'MachineService', function ($scope, $stateParams, GatewayApiService, ngDialog, MachineService) { $scope.app = $stateParams.app; $scope.apisPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; getApis(); function getApis() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { // To merge rows for api who has more than one predicateItems, here we build data manually $scope.apis = []; data.data.forEach(function(api) { api["predicateItems"].forEach(function (item, index) { var newItem = {}; newItem["id"] = api["id"]; newItem["app"] = api["app"]; newItem["ip"] = api["ip"]; newItem["port"] = api["port"]; newItem["apiName"] = api["apiName"]; newItem["pattern"] = item["pattern"]; newItem["matchStrategy"] = item["matchStrategy"]; // The itemSize indicates how many rows to merge, by using rowspan="{{api.itemSize}}" in tag newItem["itemSize"] = api["predicateItems"].length; // Mark the flag of first item to zero, indicates the start row to merge newItem["firstFlag"] = index == 0 ? 0 : 1; // Still hold the data of predicateItems, in order to bind data in edit dialog html newItem["predicateItems"] = api["predicateItems"]; $scope.apis.push(newItem); }); }); $scope.apisPageConfig.totalCount = data.data.length; } else { $scope.apis = []; $scope.apisPageConfig.totalCount = 0; } }); }; $scope.getApis = getApis; var gatewayApiDialog; $scope.editApi = function (api) { $scope.currentApi = angular.copy(api); $scope.gatewayApiDialog = { title: '编辑自定义 API', type: 'edit', confirmBtnText: '保存' }; gatewayApiDialog = ngDialog.open({ template: '/app/views/dialog/gateway/api-dialog.html', width: 900, overlay: true, scope: $scope }); }; $scope.addNewApi = function () { var mac = $scope.macInputModel.split(':'); $scope.currentApi = { grade: 0, app: $scope.app, ip: mac[0], port: mac[1], predicateItems: [{matchStrategy: 0, pattern: ''}] }; $scope.gatewayApiDialog = { title: '新增自定义 API', type: 'add', confirmBtnText: '新增' }; gatewayApiDialog = ngDialog.open({ template: '/app/views/dialog/gateway/api-dialog.html', width: 900, overlay: true, scope: $scope }); }; $scope.saveApi = function () { var apiNames = []; if ($scope.gatewayApiDialog.type === 'add') { apiNames = $scope.apis.map(function (item, index, array) { return item["apiName"]; }).filter(function (item, index, array) { return array.indexOf(item) === index; }); } if (!GatewayApiService.checkApiValid($scope.currentApi, apiNames)) { return; } if ($scope.gatewayApiDialog.type === 'add') { addNewApi($scope.currentApi); } else if ($scope.gatewayApiDialog.type === 'edit') { saveApi($scope.currentApi, true); } }; function addNewApi(api) { GatewayApiService.newApi(api).success(function (data) { if (data.code == 0) { getApis(); gatewayApiDialog.close(); } else { alert('新增自定义API失败!' + data.msg); } }); }; function saveApi(api, edit) { GatewayApiService.saveApi(api).success(function (data) { if (data.code == 0) { getApis(); if (edit) { gatewayApiDialog.close(); } else { confirmDialog.close(); } } else { alert('修改自定义API失败!' + data.msg); } }); }; var confirmDialog; $scope.deleteApi = function (api) { $scope.currentApi = api; $scope.confirmDialog = { title: '删除自定义API', type: 'delete_api', attentionTitle: '请确认是否删除如下自定义API', attention: 'API名称: ' + api.apiName, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type == 'delete_api') { deleteApi($scope.currentApi); } else { console.error('error'); } }; function deleteApi(api) { GatewayApiService.deleteApi(api).success(function (data) { if (data.code == 0) { getApis(); confirmDialog.close(); } else { alert('删除自定义API失败!' + data.msg); } }); }; $scope.addNewMatchPattern = function() { var total; if ($scope.currentApi.predicateItems == null) { $scope.currentApi.predicateItems = []; total = 0; } else { total = $scope.currentApi.predicateItems.length; } $scope.currentApi.predicateItems.splice(total + 1, 0, {matchStrategy: 0, pattern: ''}); }; $scope.removeMatchPattern = function($index) { if ($scope.currentApi.predicateItems.length <= 1) { // Should never happen since no remove button will display when only one predicateItem. alert('至少有一个匹配规则'); return; } $scope.currentApi.predicateItems.splice($index, 1); }; queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getApis(); } }); }] ); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/flow.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('GatewayFlowCtl', ['$scope', '$stateParams', 'GatewayFlowService', 'GatewayApiService', 'ngDialog', 'MachineService', function ($scope, $stateParams, GatewayFlowService, GatewayApiService, ngDialog, MachineService) { $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); GatewayFlowService.queryRules($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; } }); }; $scope.getMachineRules = getMachineRules; getApiNames(); function getApiNames() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.apiNames = []; data.data.forEach(function (api) { $scope.apiNames.push(api["apiName"]); }); } }); } $scope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}]; var gatewayFlowRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.gatewayFlowRuleDialog = { title: '编辑网关流控规则', type: 'edit', confirmBtnText: '保存' }; gatewayFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/gateway/flow-rule-dialog.html', width: 780, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { grade: 1, app: $scope.app, ip: mac[0], port: mac[1], resourceMode: 0, interval: 1, intervalUnit: 0, controlBehavior: 0, burst: 0, maxQueueingTimeoutMs: 0 }; $scope.gatewayFlowRuleDialog = { title: '新增网关流控规则', type: 'add', confirmBtnText: '新增' }; gatewayFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/gateway/flow-rule-dialog.html', width: 780, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if (!GatewayFlowService.checkRuleValid($scope.currentRule)) { return; } if ($scope.gatewayFlowRuleDialog.type === 'add') { addNewRule($scope.currentRule); } else if ($scope.gatewayFlowRuleDialog.type === 'edit') { saveRule($scope.currentRule, true); } }; $scope.useRouteID = function() { $scope.currentRule.resource = ''; }; $scope.useCustormAPI = function() { $scope.currentRule.resource = ''; }; $scope.useParamItem = function () { $scope.currentRule.paramItem = { parseStrategy: 0, matchStrategy: 0 }; }; $scope.notUseParamItem = function () { $scope.currentRule.paramItem = null; }; $scope.useParamItemVal = function() { $scope.currentRule.paramItem.pattern = ""; $scope.currentRule.paramItem.matchStrategy = 0; }; $scope.notUseParamItemVal = function() { $scope.currentRule.paramItem.pattern = null; $scope.currentRule.paramItem.matchStrategy = null; }; function addNewRule(rule) { GatewayFlowService.newRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); gatewayFlowRuleDialog.close(); } else { alert('新增网关流控规则失败!' + data.msg); } }); }; function saveRule(rule, edit) { GatewayFlowService.saveRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); if (edit) { gatewayFlowRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('修改网关流控规则失败!' + data.msg); } }); }; var confirmDialog; $scope.deleteRule = function (rule) { $scope.currentRule = rule; $scope.confirmDialog = { title: '删除网关流控规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下规则', attention: 'API名称: ' + rule.resource + ', ' + (rule.grade == 1 ? 'QPS阈值' : '线程数') + ': ' + rule.count, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type == 'delete_rule') { deleteRule($scope.currentRule); } else { console.error('error'); } }; function deleteRule(rule) { GatewayFlowService.deleteRule(rule).success(function (data) { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('删除网关流控规则失败!' + data.msg); } }); }; queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); getApiNames(); } }); }] ); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/identity.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('GatewayIdentityCtl', ['$scope', '$stateParams', 'IdentityService', 'ngDialog', 'GatewayFlowService', 'GatewayApiService', 'DegradeService', 'MachineService', '$interval', '$location', '$timeout', function ($scope, $stateParams, IdentityService, ngDialog, GatewayFlowService, GatewayApiService, DegradeService, MachineService, $interval, $location, $timeout) { $scope.app = $stateParams.app; $scope.currentPage = 1; $scope.pageSize = 16; $scope.totalPage = 1; $scope.totalCount = 0; $scope.identities = []; $scope.searchKey = ''; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; $scope.table = null; getApiNames(); function getApiNames() { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success( function (data) { if (data.code == 0 && data.data) { $scope.apiNames = []; data.data.forEach(function (api) { $scope.apiNames.push(api["apiName"]); }); } }); } var gatewayFlowRuleDialog; var gatewayFlowRuleDialogScope; $scope.addNewGatewayFlowRule = function (resource) { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); gatewayFlowRuleDialogScope = $scope.$new(true); gatewayFlowRuleDialogScope.apiNames = $scope.apiNames; gatewayFlowRuleDialogScope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}]; gatewayFlowRuleDialogScope.currentRule = { grade: 1, app: $scope.app, ip: mac[0], port: mac[1], resourceMode: gatewayFlowRuleDialogScope.apiNames.indexOf(resource) == -1 ? 0 : 1, resource: resource, interval: 1, intervalUnit: 0, controlBehavior: 0, burst: 0, maxQueueingTimeoutMs: 0 }; gatewayFlowRuleDialogScope.gatewayFlowRuleDialog = { title: '新增网关流控规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加', showAdvanceButton: true }; gatewayFlowRuleDialogScope.useRouteID = function() { gatewayFlowRuleDialogScope.currentRule.resource = ''; }; gatewayFlowRuleDialogScope.useCustormAPI = function() { gatewayFlowRuleDialogScope.currentRule.resource = ''; }; gatewayFlowRuleDialogScope.useParamItem = function () { gatewayFlowRuleDialogScope.currentRule.paramItem = { parseStrategy: 0, matchStrategy: 0 }; }; gatewayFlowRuleDialogScope.notUseParamItem = function () { gatewayFlowRuleDialogScope.currentRule.paramItem = null; }; gatewayFlowRuleDialogScope.useParamItemVal = function() { gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = ""; }; gatewayFlowRuleDialogScope.notUseParamItemVal = function() { gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = null; }; gatewayFlowRuleDialogScope.saveRule = saveGatewayFlowRule; gatewayFlowRuleDialogScope.saveRuleAndContinue = saveGatewayFlowRuleAndContinue; gatewayFlowRuleDialogScope.onOpenAdvanceClick = function () { gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = false; }; gatewayFlowRuleDialogScope.onCloseAdvanceClick = function () { gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = true; }; gatewayFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/gateway/flow-rule-dialog.html', width: 780, overlay: true, scope: gatewayFlowRuleDialogScope }); }; function saveGatewayFlowRule() { if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) { return; } GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { gatewayFlowRuleDialog.close(); let url = '/dashboard/gateway/flow/' + $scope.app; $location.path(url); } else { alert('失败!'); } }).error((data, header, config, status) => { alert('未知错误'); }); } function saveGatewayFlowRuleAndContinue() { if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) { return; } GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) { if (data.code == 0) { gatewayFlowRuleDialog.close(); } else { alert('失败!'); } }); } var degradeRuleDialog; $scope.addNewDegradeRule = function (resource) { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); degradeRuleDialogScope = $scope.$new(true); degradeRuleDialogScope.currentRule = { enable: false, grade: 0, strategy: 0, resource: resource, limitApp: 'default', app: $scope.app, ip: mac[0], port: mac[1] }; degradeRuleDialogScope.degradeRuleDialog = { title: '新增降级规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加' }; degradeRuleDialogScope.saveRule = saveDegradeRule; degradeRuleDialogScope.saveRuleAndContinue = saveDegradeRuleAndContinue; degradeRuleDialog = ngDialog.open({ template: '/app/views/dialog/degrade-rule-dialog.html', width: 680, overlay: true, scope: degradeRuleDialogScope }); }; function saveDegradeRule() { if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) { return; } DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) { if (data.code == 0) { degradeRuleDialog.close(); var url = '/dashboard/degrade/' + $scope.app; $location.path(url); } else { alert('失败!'); } }); } function saveDegradeRuleAndContinue() { if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) { return; } DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) { if (data.code == 0) { degradeRuleDialog.close(); } else { alert('失败!'); } }); } var searchHandler; $scope.searchChange = function (searchKey) { $timeout.cancel(searchHandler); searchHandler = $timeout(function () { $scope.searchKey = searchKey; reInitIdentityDatas(); }, 600); }; function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code === 0) { if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); } // Fetch all machines by current app name. queryAppMachines(); $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { reInitIdentityDatas(); } }); $scope.$on('$destroy', function () { $interval.cancel(intervalId); }); var intervalId; function reInitIdentityDatas() { getApiNames(); queryIdentities(); }; function queryIdentities() { var mac = $scope.macInputModel.split(':'); if (mac == null || mac.length < 2) { return; } IdentityService.fetchClusterNodeOfMachine(mac[0], mac[1], $scope.searchKey).success( function (data) { if (data.code == 0 && data.data) { $scope.identities = data.data; $scope.totalCount = $scope.identities.length; } else { $scope.identities = []; $scope.totalCount = 0; } } ); }; $scope.queryIdentities = queryIdentities; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/home.js ================================================ /** * @ngdoc function * @name sentinelDashboardApp.controller:MainCtrl * @description * # MainCtrl * Controller of the sentinelDashboardApp */ angular.module('sentinelDashboardApp') .controller('HomeCtrl', ['$scope', '$position', function ($scope, $position) { // do noting }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService', 'ngDialog', 'FlowServiceV1', 'DegradeService', 'AuthorityRuleService', 'ParamFlowService', 'MachineService', '$interval', '$location', '$timeout', function ($scope, $stateParams, IdentityService, ngDialog, FlowService, DegradeService, AuthorityRuleService, ParamFlowService, MachineService, $interval, $location, $timeout) { $scope.app = $stateParams.app; $scope.currentPage = 1; $scope.pageSize = 16; $scope.totalPage = 1; $scope.totalCount = 0; $scope.identities = []; // 数据自动刷新频率, 默认10s var DATA_REFRESH_INTERVAL = 30; $scope.isExpand = true; $scope.searchKey = ''; $scope.firstExpandAll = false; $scope.isTreeView = true; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; $scope.table = null; var flowRuleDialog; var flowRuleDialogScope; $scope.addNewFlowRule = function (resource) { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); flowRuleDialogScope = $scope.$new(true); flowRuleDialogScope.currentRule = { enable: false, strategy: 0, grade: 1, controlBehavior: 0, resource: resource, limitApp: 'default', clusterMode: false, clusterConfig: { thresholdType: 0 }, app: $scope.app, ip: mac[0], port: mac[1] }; flowRuleDialogScope.flowRuleDialog = { title: '新增流控规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加', showAdvanceButton: true }; // $scope.flowRuleDialog = { // showAdvanceButton : true // }; flowRuleDialogScope.saveRule = saveFlowRule; flowRuleDialogScope.saveRuleAndContinue = saveFlowRuleAndContinue; flowRuleDialogScope.onOpenAdvanceClick = function () { flowRuleDialogScope.flowRuleDialog.showAdvanceButton = false; }; flowRuleDialogScope.onCloseAdvanceClick = function () { flowRuleDialogScope.flowRuleDialog.showAdvanceButton = true; }; flowRuleDialog = ngDialog.open({ template: '/app/views/dialog/flow-rule-dialog.html', width: 680, overlay: true, scope: flowRuleDialogScope }); }; function saveFlowRule() { if (!FlowService.checkRuleValid(flowRuleDialogScope.currentRule)) { return; } FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { flowRuleDialog.close(); let url = '/dashboard/flow/' + $scope.app; $location.path(url); } else { alert('失败:' + data.msg); } }).error((data, header, config, status) => { alert('未知错误'); }); } function saveFlowRuleAndContinue() { if (!FlowService.checkRuleValid(flowRuleDialogScope.currentRule)) { return; } FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { flowRuleDialog.close(); } else { alert('失败:' + data.msg); } }); } var degradeRuleDialog; var degradeRuleDialogScope; $scope.addNewDegradeRule = function (resource) { if (!$scope.macInputModel) { return; } var mac = $scope.macInputModel.split(':'); degradeRuleDialogScope = $scope.$new(true); degradeRuleDialogScope.currentRule = { enable: false, grade: 0, strategy: 0, resource: resource, limitApp: 'default', minRequestAmount: 5, statIntervalMs: 1000, app: $scope.app, ip: mac[0], port: mac[1] }; degradeRuleDialogScope.degradeRuleDialog = { title: '新增熔断规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加' }; degradeRuleDialogScope.saveRule = saveDegradeRule; degradeRuleDialogScope.saveRuleAndContinue = saveDegradeRuleAndContinue; degradeRuleDialog = ngDialog.open({ template: '/app/views/dialog/degrade-rule-dialog.html', width: 680, overlay: true, scope: degradeRuleDialogScope }); }; function saveDegradeRule() { if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) { return; } DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { degradeRuleDialog.close(); var url = '/dashboard/degrade/' + $scope.app; $location.path(url); } else { alert('失败:' + data.msg); } }); } function saveDegradeRuleAndContinue() { if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) { return; } DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) { if (data.code === 0) { degradeRuleDialog.close(); } else { alert('失败:' + data.msg); } }); } let authorityRuleDialog; let authorityRuleDialogScope; function saveAuthorityRule() { let ruleEntity = authorityRuleDialogScope.currentRule; if (!AuthorityRuleService.checkRuleValid(ruleEntity.rule)) { return; } AuthorityRuleService.addNewRule(ruleEntity).success((data) => { if (data.success) { authorityRuleDialog.close(); let url = '/dashboard/authority/' + $scope.app; $location.path(url); } else { alert('添加规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加规则失败:' + data.msg); } else { alert("添加规则失败:未知错误"); } }); } function saveAuthorityRuleAndContinue() { let ruleEntity = authorityRuleDialogScope.currentRule; if (!AuthorityRuleService.checkRuleValid(ruleEntity.rule)) { return; } AuthorityRuleService.addNewRule(ruleEntity).success((data) => { if (data.success) { authorityRuleDialog.close(); } else { alert('添加规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加规则失败:' + data.msg); } else { alert("添加规则失败:未知错误"); } }); } $scope.addNewAuthorityRule = function (resource) { if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); authorityRuleDialogScope = $scope.$new(true); authorityRuleDialogScope.currentRule = { app: $scope.app, ip: mac[0], port: mac[1], rule: { resource: resource, strategy: 0, limitApp: '', } }; authorityRuleDialogScope.authorityRuleDialog = { title: '新增授权规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加' }; authorityRuleDialogScope.saveRule = saveAuthorityRule; authorityRuleDialogScope.saveRuleAndContinue = saveAuthorityRuleAndContinue; authorityRuleDialog = ngDialog.open({ template: '/app/views/dialog/authority-rule-dialog.html', width: 680, overlay: true, scope: authorityRuleDialogScope }); }; let paramFlowRuleDialog; let paramFlowRuleDialogScope; function saveParamFlowRule() { let ruleEntity = paramFlowRuleDialogScope.currentRule; if (!ParamFlowService.checkRuleValid(ruleEntity.rule)) { return; } ParamFlowService.addNewRule(ruleEntity).success((data) => { if (data.success) { paramFlowRuleDialog.close(); let url = '/dashboard/paramFlow/' + $scope.app; $location.path(url); } else { alert('添加热点规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加热点规则失败:' + data.msg); } else { alert("添加热点规则失败:未知错误"); } }); } function saveParamFlowRuleAndContinue() { let ruleEntity = paramFlowRuleDialogScope.currentRule; if (!ParamFlowService.checkRuleValid(ruleEntity.rule)) { return; } ParamFlowService.addNewRule(ruleEntity).success((data) => { if (data.success) { paramFlowRuleDialog.close(); } else { alert('添加热点规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加热点规则失败:' + data.msg); } else { alert("添加热点规则失败:未知错误"); } }); } $scope.addNewParamFlowRule = function (resource) { if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); paramFlowRuleDialogScope = $scope.$new(true); paramFlowRuleDialogScope.currentRule = { app: $scope.app, ip: mac[0], port: mac[1], rule: { resource: resource, grade: 1, paramFlowItemList: [], count: 0, limitApp: 'default', controlBehavior: 0, durationInSec: 1, burstCount: 0, maxQueueingTimeMs: 0, clusterMode: false, clusterConfig: { thresholdType: 0, fallbackToLocalWhenFail: true, } } }; paramFlowRuleDialogScope.paramFlowRuleDialog = { title: '新增热点规则', type: 'add', confirmBtnText: '新增', saveAndContinueBtnText: '新增并继续添加', supportAdvanced: false, showAdvanceButton: true }; paramFlowRuleDialogScope.saveRule = saveParamFlowRule; paramFlowRuleDialogScope.saveRuleAndContinue = saveParamFlowRuleAndContinue; // paramFlowRuleDialogScope.onOpenAdvanceClick = function () { // paramFlowRuleDialogScope.paramFlowRuleDialog.showAdvanceButton = false; // }; // paramFlowRuleDialogScope.onCloseAdvanceClick = function () { // paramFlowRuleDialogScope.paramFlowRuleDialog.showAdvanceButton = true; // }; paramFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/param-flow-rule-dialog.html', width: 680, overlay: true, scope: paramFlowRuleDialogScope }); }; var searchHandler; $scope.searchChange = function (searchKey) { $timeout.cancel(searchHandler); searchHandler = $timeout(function () { $scope.searchKey = searchKey; $scope.isExpand = true; $scope.firstExpandAll = true; reInitIdentityDatas(); $scope.firstExpandAll = false; }, 600); }; $scope.initTreeTable = function () { // if (!$scope.table) { com_github_culmat_jsTreeTable.register(window); $scope.table = window.treeTable($('#identities')); // } }; $scope.expandAll = function () { $scope.isExpand = true; }; $scope.collapseAll = function () { $scope.isExpand = false; }; $scope.treeView = function () { $scope.isTreeView = true; queryIdentities(); }; $scope.listView = function () { $scope.isTreeView = false; queryIdentities(); }; function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code === 0) { if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); } // Fetch all machines by current app name. queryAppMachines(); $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { reInitIdentityDatas(); } }); $scope.$on('$destroy', function () { $interval.cancel(intervalId); }); var intervalId; function reInitIdentityDatas() { // $interval.cancel(intervalId); queryIdentities(); // intervalId = $interval(function () { // queryIdentities(); // }, DATA_REFRESH_INTERVAL * 1000); }; function queryIdentities() { var mac = $scope.macInputModel.split(':'); if (mac == null || mac.length < 2) { return; } if ($scope.isTreeView) { IdentityService.fetchIdentityOfMachine(mac[0], mac[1], $scope.searchKey).success( function (data) { if (data.code == 0 && data.data) { $scope.identities = data.data; $scope.totalCount = $scope.identities.length; } else { $scope.identities = []; $scope.totalCount = 0; } } ); } else { IdentityService.fetchClusterNodeOfMachine(mac[0], mac[1], $scope.searchKey).success( function (data) { if (data.code == 0 && data.data) { $scope.identities = data.data; $scope.totalCount = $scope.identities.length; } else { $scope.identities = []; $scope.totalCount = 0; } } ); } }; $scope.queryIdentities = queryIdentities; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/login.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService', function ($scope, $state, $window, AuthService) { // If auth passed, jump to the index page directly if ($window.localStorage.getItem('session_sentinel_admin')) { $state.go('dashboard'); } $scope.login = function () { if (!$scope.username) { alert('请输入用户名'); return; } if (!$scope.password) { alert('请输入密码'); return; } var param = {"username": $scope.username, "password": $scope.password}; AuthService.login(param).success(function (data) { if (data.code == 0) { $window.localStorage.setItem('session_sentinel_admin', JSON.stringify(data.data)); $state.go('dashboard'); } else { alert(data.msg); } }); }; }] ); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/machine.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('MachineCtl', ['$scope', '$stateParams', 'MachineService', function ($scope, $stateParams, MachineService) { $scope.app = $stateParams.app; $scope.propertyName = ''; $scope.reverse = false; $scope.currentPage = 1; $scope.machines = []; $scope.machinesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.sortBy = function (propertyName) { // console.log('machine sortBy ' + propertyName); $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; $scope.propertyName = propertyName; }; $scope.reloadMachines = function() { MachineService.getAppMachines($scope.app).success( function (data) { // console.log('get machines: ' + data.data[0].hostname) if (data.code == 0 && data.data) { $scope.machines = data.data; var healthy = 0; $scope.machines.forEach(function (item) { if (item.healthy) { healthy++; } if (!item.hostname) { item.hostname = '未知' } }) $scope.healthyCount = healthy; $scope.machinesPageConfig.totalCount = $scope.machines.length; } else { $scope.machines = []; $scope.healthyCount = 0; } } ); }; $scope.removeMachine = function(ip, port) { if (!confirm("confirm to remove machine [" + ip + ":" + port + "]?")) { return; } MachineService.removeAppMachine($scope.app, ip, port).success( function(data) { if (data.code == 0) { $scope.reloadMachines(); } else { alert("remove failed"); } } ); }; $scope.reloadMachines(); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/main.js ================================================ /** * @ngdoc function * @name sentinelDashboardApp.controller:MainCtrl * @description * # MainCtrl * Controller of the sentinelDashboardApp */ angular.module('sentinelDashboardApp') .controller('DashboardCtrl', ['$scope', '$position', function ($scope, $position) { }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/metric.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('MetricCtl', ['$scope', '$stateParams', 'MetricService', '$interval', '$timeout', function ($scope, $stateParams, MetricService, $interval, $timeout) { $scope.charts = []; $scope.endTime = new Date(); $scope.startTime = new Date(); $scope.startTime.setMinutes($scope.endTime.getMinutes() - 30); $scope.startTimeFmt = formatDate($scope.startTime); $scope.endTimeFmt = formatDate($scope.endTime); function formatDate(date) { return moment(date).format('YYYY/MM/DD HH:mm:ss'); } $scope.changeStartTime = function (startTime) { $scope.startTime = new Date(startTime); $scope.startTimeFmt = formatDate(startTime); }; $scope.changeEndTime = function (endTime) { $scope.endTime = new Date(endTime); $scope.endTimeFmt = formatDate(endTime); }; $scope.app = $stateParams.app; // 数据自动刷新频率 var DATA_REFRESH_INTERVAL = 1000 * 10; $scope.servicePageConfig = { pageSize: 6, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.servicesChartConfigs = []; $scope.pageChanged = function (newPageNumber) { $scope.servicePageConfig.currentPageIndex = newPageNumber; reInitIdentityDatas(); }; var searchT; $scope.searchService = function () { $timeout.cancel(searchT); searchT = $timeout(function () { reInitIdentityDatas(); }, 600); } var intervalId; reInitIdentityDatas(); function reInitIdentityDatas() { $interval.cancel(intervalId); queryIdentityDatas(); intervalId = $interval(function () { queryIdentityDatas(); }, DATA_REFRESH_INTERVAL); }; $scope.$on('$destroy', function () { $interval.cancel(intervalId); }); $scope.initAllChart = function () { //revoke useless charts positively while($scope.charts.length > 0) { let chart = $scope.charts.pop(); chart.destroy(); } $.each($scope.metrics, function (idx, metric) { if (idx == $scope.metrics.length - 1) { return; } const chart = new G2.Chart({ container: 'chart' + idx, forceFit: true, width: 100, height: 250, padding: [10, 30, 70, 50] }); $scope.charts.push(chart); var maxQps = 0; for (var i in metric.data) { var item = metric.data[i]; if (item.passQps > maxQps) { maxQps = item.passQps; } if (item.blockQps > maxQps) { maxQps = item.blockQps; } } chart.source(metric.data); chart.scale('timestamp', { type: 'time', mask: 'YYYY-MM-DD HH:mm:ss' }); chart.scale('passQps', { min: 0, max: maxQps, fine: true, alias: '通过 QPS' // max: 10 }); chart.scale('blockQps', { min: 0, max: maxQps, fine: true, alias: '拒绝 QPS', }); chart.scale('rt', { min: 0, fine: true, }); chart.axis('rt', { grid: null, label: null }); chart.axis('blockQps', { grid: null, label: null }); chart.axis('timestamp', { label: { textStyle: { textAlign: 'center', // 文本对齐方向,可取值为: start center end fill: '#404040', // 文本的颜色 fontSize: '11', // 文本大小 //textBaseline: 'top', // 文本基准线,可取 top middle bottom,默认为middle }, autoRotate: false, formatter: function (text, item, index) { return text.substring(11, 11 + 5); } } }); chart.legend({ custom: true, position: 'bottom', allowAllCanceled: true, itemFormatter: function (val) { if ('passQps' === val) { return '通过 QPS'; } if ('blockQps' === val) { return '拒绝 QPS'; } return val; }, items: [ { value: 'passQps', marker: { symbol: 'hyphen', stroke: 'green', radius: 5, lineWidth: 2 } }, { value: 'blockQps', marker: { symbol: 'hyphen', stroke: 'blue', radius: 5, lineWidth: 2 } }, //{ value: 'rt', marker: {symbol: 'hyphen', stroke: 'gray', radius: 5, lineWidth: 2} }, ], onClick: function (ev) { const item = ev.item; const value = item.value; const checked = ev.checked; const geoms = chart.getAllGeoms(); for (var i = 0; i < geoms.length; i++) { const geom = geoms[i]; if (geom.getYScale().field === value) { if (checked) { geom.show(); } else { geom.hide(); } } } } }); chart.line().position('timestamp*passQps').size(1).color('green').shape('smooth'); chart.line().position('timestamp*blockQps').size(1).color('blue').shape('smooth'); //chart.line().position('timestamp*rt').size(1).color('gray').shape('smooth'); G2.track(false); chart.render(); }); }; $scope.metrics = []; $scope.emptyObjs = []; function queryIdentityDatas() { var params = { app: $scope.app, pageIndex: $scope.servicePageConfig.currentPageIndex, pageSize: $scope.servicePageConfig.pageSize, desc: $scope.isDescOrder, searchKey: $scope.serviceQuery }; MetricService.queryAppSortedIdentities(params).success(function (data) { $scope.metrics = []; $scope.emptyObjs = []; if (data.code === 0 && data.data) { var metricsObj = data.data.metric; var identityNames = Object.keys(metricsObj); if (identityNames.length < 1) { $scope.emptyServices = true; } else { $scope.emptyServices = false; } $scope.servicePageConfig.totalPage = data.data.totalPage; $scope.servicePageConfig.pageSize = data.data.pageSize; var totalCount = data.data.totalCount; $scope.servicePageConfig.totalCount = totalCount; for (i = 0; i < totalCount; i++) { $scope.emptyObjs.push({}); } $.each(identityNames, function (idx, identityName) { var identityDatas = metricsObj[identityName]; var metrics = {}; metrics.resource = identityName; // metrics.data = identityDatas; metrics.data = fillZeros(identityDatas); metrics.shortData = lastOfArray(identityDatas, 6); $scope.metrics.push(metrics); }); // push an empty element in the last, for ng-init reasons. $scope.metrics.push([]); } else { $scope.emptyServices = true; console.log(data.msg); } }); }; function fillZeros(metricData) { if (!metricData || metricData.length == 0) { return []; } var filledData = []; filledData.push(metricData[0]); var lastTime = metricData[0].timestamp / 1000; for (var i = 1; i < metricData.length; i++) { var curTime = metricData[i].timestamp / 1000; if (curTime > lastTime + 1) { for (var j = lastTime + 1; j < curTime; j++) { filledData.push({ "timestamp": j * 1000, "passQps": 0, "blockQps": 0, "successQps": 0, "exception": 0, "rt": 0, "count": 0 }) } } filledData.push(metricData[i]); lastTime = curTime; } return filledData; } function lastOfArray(arr, n) { if (!arr.length) { return []; } var rs = []; for (i = 0; i < n && i < arr.length; i++) { rs.push(arr[arr.length - 1 - i]); } return rs; } $scope.isDescOrder = true; $scope.setDescOrder = function () { $scope.isDescOrder = true; reInitIdentityDatas(); } $scope.setAscOrder = function () { $scope.isDescOrder = false; reInitIdentityDatas(); } }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js ================================================ /** * Parameter flow control controller. * * @author Eric Zhao */ angular.module('sentinelDashboardApp').controller('ParamFlowController', ['$scope', '$stateParams', 'ParamFlowService', 'ngDialog', 'MachineService', function ($scope, $stateParams, ParamFlowService, ngDialog, MachineService) { const UNSUPPORTED_CODE = 4041; $scope.app = $stateParams.app; $scope.curExItem = {}; $scope.paramItemClassTypeList = [ 'int', 'double', 'java.lang.String', 'long', 'float', 'char', 'byte' ]; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; function updateSingleParamItem(arr, v, t, c) { for (let i = 0; i < arr.length; i++) { if (arr[i].object === v && arr[i].classType === t) { arr[i].count = c; return; } } arr.push({object: v, classType: t, count: c}); } function removeSingleParamItem(arr, v, t) { for (let i = 0; i < arr.length; i++) { if (arr[i].object === v && arr[i].classType === t) { arr.splice(i, 1); break; } } } function isNumberClass(classType) { return classType === 'int' || classType === 'double' || classType === 'float' || classType === 'long' || classType === 'short'; } function isByteClass(classType) { return classType === 'byte'; } function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; } function notGoodNumber(num) { return num === undefined || num === '' || isNaN(num); } function notGoodNumberBetweenExclusive(num, l ,r) { return num === undefined || num === '' || isNaN(num) || num < l || num > r; } $scope.notValidParamItem = (curExItem) => { if (isNumberClass(curExItem.classType) && notGoodNumber(curExItem.object)) { return true; } if (isByteClass(curExItem.classType) && notGoodNumberBetweenExclusive(curExItem.object, -128, 127)) { return true; } return curExItem.object === undefined || curExItem.classType === undefined || notNumberAtLeastZero(curExItem.count); }; $scope.addParamItem = () => { updateSingleParamItem($scope.currentRule.rule.paramFlowItemList, $scope.curExItem.object, $scope.curExItem.classType, $scope.curExItem.count); let oldItem = $scope.curExItem; $scope.curExItem = {classType: oldItem.classType}; }; $scope.removeParamItem = (v, t) => { removeSingleParamItem($scope.currentRule.rule.paramFlowItemList, v, t); }; function getMachineRules() { if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); ParamFlowService.queryMachineRules($scope.app, mac[0], mac[1]) .success(function (data) { if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; if (data.code === UNSUPPORTED_CODE) { $scope.loadError = {message: "机器 " + mac[0] + ":" + mac[1] + " 的 Sentinel 客户端版本不支持热点参数限流功能,请升级至 0.2.0 以上版本并引入 sentinel-parameter-flow-control 依赖。"} } else { $scope.loadError = {message: data.msg} } } }) .error((data, header, config, status) => { $scope.loadError = {message: "未知错误"} }); } $scope.getMachineRules = getMachineRules; getMachineRules(); var paramFlowRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); if ($scope.currentRule.rule && $scope.currentRule.rule.durationInSec === undefined) { $scope.currentRule.rule.durationInSec = 1; } $scope.paramFlowRuleDialog = { title: '编辑热点规则', type: 'edit', confirmBtnText: '保存', supportAdvanced: true, showAdvanceButton: rule.rule.paramFlowItemList === undefined || rule.rule.paramFlowItemList.length <= 0 }; paramFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/param-flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); $scope.curExItem = {}; }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { app: $scope.app, ip: mac[0], port: mac[1], rule: { grade: 1, paramFlowItemList: [], count: 0, limitApp: 'default', controlBehavior: 0, durationInSec: 1, burstCount: 0, maxQueueingTimeMs: 0, clusterMode: false, clusterConfig: { thresholdType: 0, fallbackToLocalWhenFail: true, } } }; $scope.paramFlowRuleDialog = { title: '新增热点规则', type: 'add', confirmBtnText: '新增', supportAdvanced: true, showAdvanceButton: true, }; paramFlowRuleDialog = ngDialog.open({ template: '/app/views/dialog/param-flow-rule-dialog.html', width: 680, overlay: true, scope: $scope }); $scope.curExItem = {}; }; $scope.onOpenAdvanceClick = function () { $scope.paramFlowRuleDialog.showAdvanceButton = false; }; $scope.onCloseAdvanceClick = function () { $scope.paramFlowRuleDialog.showAdvanceButton = true; }; $scope.saveRule = function () { if (!ParamFlowService.checkRuleValid($scope.currentRule.rule)) { return; } if ($scope.paramFlowRuleDialog.type === 'add') { addNewRuleAndPush($scope.currentRule); } else if ($scope.paramFlowRuleDialog.type === 'edit') { saveRuleAndPush($scope.currentRule, true); } }; function addNewRuleAndPush(rule) { ParamFlowService.addNewRule(rule).success((data) => { if (data.success) { getMachineRules(); paramFlowRuleDialog.close(); } else { alert('添加规则失败:' + data.msg); } }).error((data) => { if (data) { alert('添加规则失败:' + data.msg); } else { alert("添加规则失败:未知错误"); } }); } function saveRuleAndPush(rule, edit) { ParamFlowService.saveRule(rule).success(function (data) { if (data.success) { alert("修改规则成功"); getMachineRules(); if (edit) { paramFlowRuleDialog.close(); } else { confirmDialog.close(); } } else { alert('修改规则失败:' + data.msg); } }).error((data) => { if (data) { alert('修改规则失败:' + data.msg); } else { alert("修改规则失败:未知错误"); } }); } function deleteRuleAndPush(entity) { if (entity.id === undefined || isNaN(entity.id)) { alert('规则 ID 不合法!'); return; } ParamFlowService.deleteRule(entity).success((data) => { if (data.code == 0) { getMachineRules(); confirmDialog.close(); } else { alert('删除规则失败:' + data.msg); } }).error((data) => { if (data) { alert('删除规则失败:' + data.msg); } else { alert("删除规则失败:未知错误"); } }); }; var confirmDialog; $scope.deleteRule = function (ruleEntity) { $scope.currentRule = ruleEntity; console.log('deleting: ' + ruleEntity); $scope.confirmDialog = { title: '删除热点规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下热点参数限流规则', attention: '资源名: ' + ruleEntity.rule.resource + ', 热点参数索引: ' + ruleEntity.rule.paramIdx + ', 限流模式: ' + (ruleEntity.rule.grade === 1 ? 'QPS' : '未知') + ', 限流阈值: ' + ruleEntity.rule.count, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type === 'delete_rule') { deleteRuleAndPush($scope.currentRule); } else { console.error('error'); } }; queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code == 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/system.js ================================================ var app = angular.module('sentinelDashboardApp'); app.controller('SystemCtl', ['$scope', '$stateParams', 'SystemService', 'ngDialog', 'MachineService', function ($scope, $stateParams, SystemService, ngDialog, MachineService) { //初始化 $scope.app = $stateParams.app; $scope.rulesPageConfig = { pageSize: 10, currentPageIndex: 1, totalPage: 1, totalCount: 0, }; $scope.macsInputConfig = { searchField: ['text', 'value'], persist: true, create: false, maxItems: 1, render: { item: function (data, escape) { return '
          ' + escape(data.text) + '
          '; } }, onChange: function (value, oldValue) { $scope.macInputModel = value; } }; getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { return; } let mac = $scope.macInputModel.split(':'); SystemService.queryMachineRules($scope.app, mac[0], mac[1]).success( function (data) { if (data.code === 0 && data.data) { $scope.rules = data.data; $.each($scope.rules, function (idx, rule) { if (rule.highestSystemLoad >= 0) { rule.grade = 0; } else if (rule.avgRt >= 0) { rule.grade = 1; } else if (rule.maxThread >= 0) { rule.grade = 2; } else if (rule.qps >= 0) { rule.grade = 3; } else if (rule.highestCpuUsage >= 0) { rule.grade = 4; } }); $scope.rulesPageConfig.totalCount = $scope.rules.length; } else { $scope.rules = []; $scope.rulesPageConfig.totalCount = 0; } }); } $scope.getMachineRules = getMachineRules; var systemRuleDialog; $scope.editRule = function (rule) { $scope.currentRule = angular.copy(rule); $scope.systemRuleDialog = { title: '编辑系统保护规则', type: 'edit', confirmBtnText: '保存' }; systemRuleDialog = ngDialog.open({ template: '/app/views/dialog/system-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.addNewRule = function () { var mac = $scope.macInputModel.split(':'); $scope.currentRule = { grade: 0, app: $scope.app, ip: mac[0], port: mac[1], }; $scope.systemRuleDialog = { title: '新增系统保护规则', type: 'add', confirmBtnText: '新增' }; systemRuleDialog = ngDialog.open({ template: '/app/views/dialog/system-rule-dialog.html', width: 680, overlay: true, scope: $scope }); }; $scope.saveRule = function () { if ($scope.systemRuleDialog.type === 'add') { addNewRule($scope.currentRule); } else if ($scope.systemRuleDialog.type === 'edit') { saveRule($scope.currentRule, true); } }; var confirmDialog; $scope.deleteRule = function (rule) { $scope.currentRule = rule; var ruleTypeDesc = ''; var ruleTypeCount = null; if (rule.highestSystemLoad != -1) { ruleTypeDesc = 'LOAD'; ruleTypeCount = rule.highestSystemLoad; } else if (rule.avgRt != -1) { ruleTypeDesc = 'RT'; ruleTypeCount = rule.avgRt; } else if (rule.maxThread != -1) { ruleTypeDesc = '线程数'; ruleTypeCount = rule.maxThread; } else if (rule.qps != -1) { ruleTypeDesc = 'QPS'; ruleTypeCount = rule.qps; }else if (rule.highestCpuUsage != -1) { ruleTypeDesc = 'CPU 使用率'; ruleTypeCount = rule.highestCpuUsage; } $scope.confirmDialog = { title: '删除系统保护规则', type: 'delete_rule', attentionTitle: '请确认是否删除如下系统保护规则', attention: '阈值类型: ' + ruleTypeDesc + ', 阈值: ' + ruleTypeCount, confirmBtnText: '删除', }; confirmDialog = ngDialog.open({ template: '/app/views/dialog/confirm-dialog.html', scope: $scope, overlay: true }); }; $scope.confirm = function () { if ($scope.confirmDialog.type === 'delete_rule') { deleteRule($scope.currentRule); // } else if ($scope.confirmDialog.type == 'enable_rule') { // $scope.currentRule.enable = true; // saveRule($scope.currentRule); // } else if ($scope.confirmDialog.type == 'disable_rule') { // $scope.currentRule.enable = false; // saveRule($scope.currentRule); // } else if ($scope.confirmDialog.type == 'enable_all') { // enableAll($scope.app); // } else if ($scope.confirmDialog.type == 'disable_all') { // disableAll($scope.app); } else { console.error('error'); } }; function deleteRule(rule) { SystemService.deleteRule(rule).success(function (data) { if (data.code === 0) { getMachineRules(); confirmDialog.close(); } else if (data.msg != null) { alert('失败:' + data.msg); } else { alert('失败:未知错误'); } }); } function addNewRule(rule) { if (rule.grade == 4 && (rule.highestCpuUsage < 0 || rule.highestCpuUsage > 1)) { alert('CPU 使用率模式的取值范围应为 [0.0, 1.0],对应 0% - 100%'); return; } SystemService.newRule(rule).success(function (data) { if (data.code === 0) { getMachineRules(); systemRuleDialog.close(); } else if (data.msg != null) { alert('失败:' + data.msg); } else { alert('失败:未知错误'); } }); } function saveRule(rule, edit) { SystemService.saveRule(rule).success(function (data) { if (data.code === 0) { getMachineRules(); if (edit) { systemRuleDialog.close(); } else { confirmDialog.close(); } } else if (data.msg != null) { alert('失败:' + data.msg); } else { alert('失败:未知错误'); } }); } queryAppMachines(); function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { if (data.code === 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.healthy) { $scope.macsInputOptions.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; } } else { $scope.macsInputOptions = []; } } ); }; $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { getMachineRules(); } }); }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.html ================================================
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.js ================================================ /** * @ngdoc directive * @name izzyposWebApp.directive:adminPosHeader * @description * # adminPosHeader */ angular.module('sentinelDashboardApp') .directive('header', ['VersionService', 'AuthService', function () { return { templateUrl: 'app/scripts/directives/header/header.html', restrict: 'E', replace: true, controller: function ($scope, $state, $window, VersionService, AuthService) { VersionService.version().success(function (data) { if (data.code == 0) { $scope.dashboardVersion = data.data; } }); if (!$window.localStorage.getItem("session_sentinel_admin")) { AuthService.check().success(function (data) { if (data.code == 0) { $window.localStorage.setItem('session_sentinel_admin', JSON.stringify(data.data)); handleLogout($scope, data.data.id) } else { $state.go('login'); } }); } else { try { var id = JSON.parse($window.localStorage.getItem("session_sentinel_admin")).id; handleLogout($scope, id); } catch (e) { // Historical version compatibility processing, fixes issue-1449 // If error happens while parsing, remove item in localStorage and redirect to login page. $window.localStorage.removeItem("session_sentinel_admin"); $state.go('login'); } } function handleLogout($scope, id) { if (id == 'FAKE_EMP_ID') { $scope.showLogout = false; } else { $scope.showLogout = true; } } $scope.logout = function () { AuthService.logout().success(function (data) { if (data.code == 0) { $window.localStorage.removeItem("session_sentinel_admin"); $state.go('login'); } else { alert('logout error'); } }); } } } }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar-search/sidebar-search.html ================================================ ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar-search/sidebar-search.js ================================================ /** * @ngdoc directive * @name izzyposWebApp.directive:adminPosHeader * @description * # adminPosHeader */ angular.module('sentinelDashboardApp') .directive('sidebarSearch', function () { return { templateUrl: 'app/scripts/directives/sidebar/sidebar-search/sidebar-search.html', restrict: 'E', replace: true, scope: { }, controller: function ($scope) { $scope.selectedMenu = 'home'; } } }); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html ================================================ ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js ================================================ angular.module('sentinelDashboardApp') .directive('sidebar', ['$location', '$stateParams', 'AppService', function () { return { templateUrl: 'app/scripts/directives/sidebar/sidebar.html', restrict: 'E', replace: true, scope: { }, controller: function ($scope, $stateParams, $location, AppService) { $scope.app = $stateParams.app; $scope.collapseVar = 0; // app AppService.getApps().success( function (data) { if (data.code === 0) { let path = $location.path().split('/'); let initHashApp = path[path.length - 1]; $scope.apps = data.data; $scope.apps = $scope.apps.map(function (item) { if (item.app === initHashApp) { item.active = true; } let healthyCount = 0; for (let i in item.machines) { if (item.machines[i].healthy) { healthyCount++; } } item.healthyCount = healthyCount; // Handle appType item.isGateway = item.appType === 1 || item.appType === 11 || item.appType === 12; if (item.shown) { return item; } }); } } ); // toggle side bar $scope.click = function ($event) { let entry = angular.element($event.target).scope().entry; entry.active = !entry.active;// toggle this clicked app bar $scope.apps.forEach(function (item) { // collapse other app bars if (item !== entry) { item.active = false; } }); }; /** * @deprecated */ $scope.addSearchApp = function () { let findApp = false; for (let i = 0; i < $scope.apps.length; i++) { if ($scope.apps[i].app === $scope.searchApp) { findApp = true; break; } } if (!findApp) { $scope.apps.push({ app: $scope.searchApp }); } }; } }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/filters/filters.js ================================================ var app = angular.module('sentinelDashboardApp'); app.filter('range', [function () { return function (input, length) { if (isNaN(length) || length <= 0) { return []; } input = []; for (var index = 1; index <= length; index++) { input.push(index); } return input; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/libs/treeTable.js ================================================ var com_github_culmat_jsTreeTable = (function(){ function depthFirst(tree, func, childrenAttr) { childrenAttr = childrenAttr || 'children' function i_depthFirst(node) { if (node[childrenAttr]) { $.each(node[childrenAttr], function(i, child) { i_depthFirst(child) }) } func(node) } $.each(tree, function(i, root) { i_depthFirst(root) }) return tree } /* * make a deep copy of the object */ function copy(data){ return JSON.parse(JSON.stringify(data)) } function makeTree (data, idAttr, refAttr, childrenAttr) { var data_tmp = data idAttr = idAttr || 'id' refAttr = refAttr || 'parent' childrenAttr = childrenAttr || 'children' var byName = [] $.each(data_tmp, function(i, entry) { byName[entry[idAttr]] = entry }) var tree = [] $.each(data_tmp, function(i, entry) { var parents = entry[refAttr] if(!$.isArray(parents)){ parents = [parents] } if(parents.length == 0){ tree.push(entry) } else { var inTree = false; $.each(parents, function(i,parentID){ var parent = byName[parentID] if (parent) { if (!parent[childrenAttr]) { parent[childrenAttr] = [] } if($.inArray(entry, parent[childrenAttr])< 0) parent[childrenAttr].push(entry) inTree = true } }) if(!inTree){ tree.push(entry) } } }) return tree } function renderTree(tree, childrenAttr, idAttr, attrs, renderer, tableAttributes) { childrenAttr = childrenAttr || 'children' idAttr = idAttr || 'id' tableAttributes = tableAttributes || {} var maxLevel = 0; var ret = [] var table = $("") $.each(tableAttributes, function(key, value){ if(key == 'class' && value != 'jsTT') { table.addClass(value) } else { table.attr(key, value) } }) var thead = $("") var tr = $("") var tbody = $("") table.append(thead) thead.append(tr) table.append(tbody) if (attrs) { $.each(attrs, function(attr, desc) { $(tr).append($('')) }) } else { $(tr).append($('')) $.each(tree[0], function(key, value) { if (key != childrenAttr && key != idAttr) $(tr).append($('')) }) } function render(node, parent) { var tr = $("") $(tr).attr('data-tt-id', node[idAttr]) $(tr).attr('data-tt-level', node['data-tt-level']) if(!node[childrenAttr] || node[childrenAttr].length == 0) $(tr).attr('data-tt-isleaf', true) else $(tr).attr('data-tt-isnode', true) if (parent) { $(tr).attr('data-tt-parent-id', parent[idAttr]) } if (renderer) { renderer($(tr), node) }else if (attrs) { $.each(attrs, function(attr, desc) { $(tr).append($('')) }) } else { $(tr).append($('')) $.each(node, function(key, value) { if (key != childrenAttr && key != idAttr && key != 'data-tt-level') $(tr).append($('')) }) } tbody.append(tr) } function i_renderTree(subTree, childrenAttr, level, parent) { maxLevel = Math.max(maxLevel, level) $.each(subTree, function(i, node) { node['data-tt-level'] = level render(node, parent) if (node[childrenAttr]) { $.each(node[childrenAttr], function(i, child) { i_renderTree([ child ], childrenAttr, level + 1, node) }) } }) } i_renderTree(tree, childrenAttr, 1) if (tree[0]) tree[0].maxLevel = maxLevel return table } function attr2attr(nodes, attrs){ $.each(nodes, function(i, node) { $.each(attrs, function(j, at) { node[at] = $(node).attr(at) }) }) return nodes } function treeTable(table){ table.addClass('jsTT') table.expandLevel = function (n) { $("tr[data-tt-level]", table).each(function(index) { var level = parseInt($(this).attr('data-tt-level')) if (level > n-1) { this.trCollapse(true) } else if (level == n-1){ this.trExpand(true) } }) } function getLevel(node){ var level = node.attr('data-tt-level') if(level != undefined ) return parseInt(level) var parentID = node.attr('data-tt-parent-id') if( parentID == undefined){ return 0 } else { return getLevel($('tr[data-tt-id="'+parentID+'"]', table).first()) + 1 } } $("tr[data-tt-id]", table).each(function(i,node){ node = $(node) node.attr('data-tt-level', getLevel(node)) }) var dat = $("tr[data-tt-level]", table).get() $.each(dat, function(j, d) { d.trChildrenVisible = true d.trChildren = [] }) dat = attr2attr(dat, ['data-tt-id', 'data-tt-parent-id']) dat = makeTree(dat, 'data-tt-id', 'data-tt-parent-id', 'trChildren') var imgExpand = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHlJREFUeNrcU1sNgDAQ6wgmcAM2MICGGlg1gJnNzWQcvwQGy1j4oUl/7tH0mpwzM7SgQyO+EZAUWh2MkkzSWhJwuRAlHYsJwEwyvs1gABDuzqoJcTw5qxaIJN0bgQRgIjnlmn1heSO5PE6Y2YXe+5Cr5+h++gs12AcAS6FS+7YOsj4AAAAASUVORK5CYII=" var imgCollapse = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHFJREFUeNpi/P//PwMlgImBQsA44C6gvhfa29v3MzAwOODRc6CystIRbxi0t7fjDJjKykpGYrwwi1hxnLHQ3t7+jIGBQRJJ6HllZaUUKYEYRYBPOB0gBShKwKGA////48VtbW3/8clTnBIH3gCKkzJgAGvBX0dDm0sCAAAAAElFTkSuQmCC" $("tr[data-tt-level]", table).each(function(index, tr) { var level = $(tr).attr('data-tt-level') var td = $("td",tr).first() if(tr.trChildren.length>0){ td.prepend($('')) } else { td.prepend($('')) } td.prepend($('')) // td.css('white-space','nowrap') tr.trExpand = function(changeState){ if(this.trChildren.length < 1) return if(changeState) { this.trChildrenVisible = true $('#state', this).get(0).src= imgCollapse } var doit = changeState || this.trChildrenVisible $.each(this.trChildren, function(i, ctr) { if(doit) $(ctr).css('display', 'table-row') ctr.trExpand() }) } tr.trCollapse = function(changeState){ if(this.trChildren.length < 1) return if(changeState) { this.trChildrenVisible = false $('#state', this).get(0).src= imgExpand } $.each(this.trChildren, function(i, ctr) { $(ctr).css('display', 'none') ctr.trCollapse() }) } $(tr).click(function() { this.trChildrenVisible ? this.trCollapse(true) : this.trExpand(true) }) }) return table } function appendTreetable(tree, options) { function inALine(nodes) { var tr = $('') $.each(nodes, function(i, node){ tr.append($('
          ' + desc + '' + idAttr + '' + key + '
          ' + node[attr] + '' + node[idAttr] + '' + value + '
          ').append(node)) }) return $('').append(tr) } options = options || {} options.idAttr = (options.idAttr || 'id') options.childrenAttr = (options.childrenAttr || 'children') var controls = (options.controls || []) if (!options.mountPoint) options.mountPoint = $('body') if (options.depthFirst) depthFirst(tree, options.depthFirst, options.childrenAttr) var rendered = renderTree(tree, options.childrenAttr, options.idAttr, options.renderedAttr, options.renderer, options.tableAttributes) treeTable(rendered) if (options.replaceContent) { options.mountPoint.html('') } var initialExpandLevel = options.initialExpandLevel ? parseInt(options.initialExpandLevel) : -1 initialExpandLevel = Math.min(initialExpandLevel, tree[0].maxLevel) rendered.expandLevel(initialExpandLevel) if(options.slider){ var slider = $('
          ') slider.width('200px') slider.slider({ min : 1, max : tree[0].maxLevel, range : "min", value : initialExpandLevel, slide : function(event, ui) { rendered.expandLevel(ui.value) } }) controls = [slider].concat(options.controls) } if(controls.length >0){ options.mountPoint.append(inALine(controls)) } options.mountPoint.append(rendered) return rendered } return { depthFirst : depthFirst, makeTree : makeTree, renderTree : renderTree, attr2attr : attr2attr, treeTable : treeTable, appendTreetable : appendTreetable, jsTreeTable : '1.0', register : function(target){ $.each(this, function(key, value){ if(key != 'register') target[key] = value}) } } })(); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/appservice.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('AppService', ['$http', function ($http) { this.getApps = function () { return $http({ // url: 'app/mock_infos', url: 'app/briefinfos.json', method: 'GET' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/auth_service.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('AuthService', ['$http', function ($http) { this.check = function () { return $http({ url: '/auth/check', method: 'POST' }); }; this.login = function (param) { return $http({ url: '/auth/login', params: param, method: 'POST' }); }; this.logout = function () { return $http({ url: '/auth/logout', method: 'POST' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/authority_service.js ================================================ /** * Authority rule service. */ angular.module('sentinelDashboardApp').service('AuthorityRuleService', ['$http', function ($http) { this.queryMachineRules = function(app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/authority/rules', params: param, method: 'GET' }); }; this.addNewRule = function(rule) { return $http({ url: '/authority/rule', data: rule, method: 'POST' }); }; this.saveRule = function (entity) { return $http({ url: '/authority/rule/' + entity.id, data: entity, method: 'PUT' }); }; this.deleteRule = function (entity) { return $http({ url: '/authority/rule/' + entity.id, method: 'DELETE' }); }; this.checkRuleValid = function checkRuleValid(rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; } if (rule.limitApp === undefined || rule.limitApp === '') { alert('流控针对应用不能为空'); return false; } if (rule.strategy === undefined) { alert('必须选择黑白名单模式'); return false; } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js ================================================ /** * Cluster state control service. * * @author Eric Zhao */ angular.module('sentinelDashboardApp').service('ClusterStateService', ['$http', function ($http) { this.fetchClusterUniversalStateSingle = function(app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/cluster/state_single', params: param, method: 'GET' }); }; this.fetchClusterUniversalStateOfApp = function(app) { return $http({ url: '/cluster/state/' + app, method: 'GET' }); }; this.fetchClusterServerStateOfApp = function(app) { return $http({ url: '/cluster/server_state/' + app, method: 'GET' }); }; this.fetchClusterClientStateOfApp = function(app) { return $http({ url: '/cluster/client_state/' + app, method: 'GET' }); }; this.modifyClusterConfig = function(config) { return $http({ url: '/cluster/config/modify_single', data: config, method: 'POST' }); }; this.applyClusterFullAssignOfApp = function(app, clusterMap) { return $http({ url: '/cluster/assign/all_server/' + app, data: clusterMap, method: 'POST' }); }; this.applyClusterSingleServerAssignOfApp = function(app, request) { return $http({ url: '/cluster/assign/single_server/' + app, data: request, method: 'POST' }); }; this.applyClusterServerBatchUnbind = function(app, machineSet) { return $http({ url: '/cluster/assign/unbind_server/' + app, data: machineSet, method: 'POST' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degrade_service.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('DegradeService', ['$http', function ($http) { this.queryMachineRules = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: 'degrade/rules.json', params: param, method: 'GET' }); }; this.newRule = function (rule) { return $http({ url: '/degrade/rule', data: rule, method: 'POST' }); }; this.saveRule = function (rule) { var param = { id: rule.id, resource: rule.resource, limitApp: rule.limitApp, grade: rule.grade, count: rule.count, timeWindow: rule.timeWindow, statIntervalMs: rule.statIntervalMs, minRequestAmount: rule.minRequestAmount, slowRatioThreshold: rule.slowRatioThreshold, }; return $http({ url: '/degrade/rule/' + rule.id, data: param, method: 'PUT' }); }; this.deleteRule = function (rule) { return $http({ url: '/degrade/rule/' + rule.id, method: 'DELETE' }); }; this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; } if (rule.grade === undefined || rule.grade < 0) { alert('未知的降级策略'); return false; } if (rule.count === undefined || rule.count === '' || rule.count < 0) { alert('降级阈值不能为空或小于 0'); return false; } if (rule.timeWindow == undefined || rule.timeWindow === '' || rule.timeWindow <= 0) { alert('熔断时长必须大于 0s'); return false; } if (rule.minRequestAmount == undefined || rule.minRequestAmount <= 0) { alert('最小请求数目需大于 0'); return false; } if (rule.statIntervalMs == undefined || rule.statIntervalMs <= 0) { alert('统计窗口时长需大于 0s'); return false; } if (rule.statIntervalMs !== undefined && rule.statIntervalMs > 60 * 1000 * 2) { alert('统计窗口时长最大 120s'); return false; } // 异常比率类型. if (rule.grade == 1 && rule.count > 1) { alert('异常比率超出范围:[0.0 - 1.0]'); return false; } if (rule.grade == 0) { if (rule.slowRatioThreshold == undefined) { alert('慢调用比率不能为空'); return false; } if (rule.slowRatioThreshold < 0 || rule.slowRatioThreshold > 1) { alert('慢调用比率超出范围:[0.0 - 1.0]'); return false; } } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('FlowServiceV1', ['$http', function ($http) { this.queryMachineRules = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/v1/flow/rules', params: param, method: 'GET' }); }; this.newRule = function (rule) { var param = { resource: rule.resource, limitApp: rule.limitApp, grade: rule.grade, count: rule.count, strategy: rule.strategy, refResource: rule.refResource, controlBehavior: rule.controlBehavior, warmUpPeriodSec: rule.warmUpPeriodSec, maxQueueingTimeMs: rule.maxQueueingTimeMs, app: rule.app, ip: rule.ip, port: rule.port }; return $http({ url: '/v1/flow/rule', data: rule, method: 'POST' }); }; this.saveRule = function (rule) { var param = { id: rule.id, resource: rule.resource, limitApp: rule.limitApp, grade: rule.grade, count: rule.count, strategy: rule.strategy, refResource: rule.refResource, controlBehavior: rule.controlBehavior, warmUpPeriodSec: rule.warmUpPeriodSec, maxQueueingTimeMs: rule.maxQueueingTimeMs, }; return $http({ url: '/v1/flow/save.json', params: param, method: 'PUT' }); }; this.deleteRule = function (rule) { var param = { id: rule.id, app: rule.app }; return $http({ url: '/v1/flow/delete.json', params: param, method: 'DELETE' }); }; function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; } function notNumberGreaterThanZero(num) { return num === undefined || num === '' || isNaN(num) || num <= 0; } this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; } if (rule.count === undefined || rule.count < 0) { alert('限流阈值必须大于等于 0'); return false; } if (rule.strategy === undefined || rule.strategy < 0) { alert('无效的流控模式'); return false; } if (rule.strategy == 1 || rule.strategy == 2) { if (rule.refResource === undefined || rule.refResource == '') { alert('请填写关联资源或入口'); return false; } } if (rule.controlBehavior === undefined || rule.controlBehavior < 0) { alert('无效的流控整形方式'); return false; } if (rule.controlBehavior == 1 && notNumberGreaterThanZero(rule.warmUpPeriodSec)) { alert('预热时长必须大于 0'); return false; } if (rule.controlBehavior == 2 && notNumberGreaterThanZero(rule.maxQueueingTimeMs)) { alert('排队超时时间必须大于 0'); return false; } if (rule.clusterMode && (rule.clusterConfig === undefined || rule.clusterConfig.thresholdType === undefined)) { alert('集群限流配置不正确'); return false; } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('FlowServiceV2', ['$http', function ($http) { this.queryMachineRules = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/v2/flow/rules', params: param, method: 'GET' }); }; this.newRule = function (rule) { return $http({ url: '/v2/flow/rule', data: rule, method: 'POST' }); }; this.saveRule = function (rule) { return $http({ url: '/v2/flow/rule/' + rule.id, data: rule, method: 'PUT' }); }; this.deleteRule = function (rule) { return $http({ url: '/v2/flow/rule/' + rule.id, method: 'DELETE' }); }; function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; } function notNumberGreaterThanZero(num) { return num === undefined || num === '' || isNaN(num) || num <= 0; } this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; } if (rule.count === undefined || rule.count < 0) { alert('限流阈值必须大于等于 0'); return false; } if (rule.strategy === undefined || rule.strategy < 0) { alert('无效的流控模式'); return false; } if (rule.strategy == 1 || rule.strategy == 2) { if (rule.refResource === undefined || rule.refResource == '') { alert('请填写关联资源或入口'); return false; } } if (rule.controlBehavior === undefined || rule.controlBehavior < 0) { alert('无效的流控整形方式'); return false; } if (rule.controlBehavior == 1 && notNumberGreaterThanZero(rule.warmUpPeriodSec)) { alert('预热时长必须大于 0'); return false; } if (rule.controlBehavior == 2 && notNumberGreaterThanZero(rule.maxQueueingTimeMs)) { alert('排队超时时间必须大于 0'); return false; } if (rule.clusterMode && (rule.clusterConfig === undefined || rule.clusterConfig.thresholdType === undefined)) { alert('集群限流配置不正确'); return false; } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/api_service.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('GatewayApiService', ['$http', function ($http) { this.queryApis = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/gateway/api/list.json', params: param, method: 'GET' }); }; this.newApi = function (api) { return $http({ url: '/gateway/api/new.json', data: api, method: 'POST' }); }; this.saveApi = function (api) { return $http({ url: '/gateway/api/save.json', data: api, method: 'POST' }); }; this.deleteApi = function (api) { var param = { id: api.id, app: api.app }; return $http({ url: '/gateway/api/delete.json', params: param, method: 'POST' }); }; this.checkApiValid = function (api, apiNames) { if (api.apiName === undefined || api.apiName === '') { alert('API名称不能为空'); return false; } if (api.predicateItems == null || api.predicateItems.length === 0) { // Should never happen since no remove button will display when only one predicateItem. alert('至少有一个匹配规则'); return false; } for (var i = 0; i < api.predicateItems.length; i++) { var predicateItem = api.predicateItems[i]; var pattern = predicateItem.pattern; if (pattern === undefined || pattern === '') { alert('匹配串不能为空,请检查'); return false; } } if (apiNames.indexOf(api.apiName) !== -1) { alert('API名称(' + api.apiName + ')已存在'); return false; } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/flow_service.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('GatewayFlowService', ['$http', function ($http) { this.queryRules = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/gateway/flow/list.json', params: param, method: 'GET' }); }; this.newRule = function (rule) { return $http({ url: '/gateway/flow/new.json', data: rule, method: 'POST' }); }; this.saveRule = function (rule) { return $http({ url: '/gateway/flow/save.json', data: rule, method: 'POST' }); }; this.deleteRule = function (rule) { var param = { id: rule.id, app: rule.app }; return $http({ url: '/gateway/flow/delete.json', params: param, method: 'POST' }); }; this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('API名称不能为空'); return false; } if (rule.paramItem != null) { if (rule.paramItem.parseStrategy == 2 || rule.paramItem.parseStrategy == 3 || rule.paramItem.parseStrategy == 4) { if (rule.paramItem.fieldName === undefined || rule.paramItem.fieldName === '') { alert('当参数属性为Header、URL参数、Cookie时,参数名称不能为空'); return false; } if (rule.paramItem.pattern === '') { alert('匹配串不能为空'); return false; } } } if (rule.count === undefined || rule.count < 0) { alert((rule.grade === 1 ? 'QPS阈值' : '线程数') + '必须大于等于 0'); return false; } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/identityservice.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('IdentityService', ['$http', function ($http) { this.fetchIdentityOfMachine = function (ip, port, searchKey) { var param = { ip: ip, port: port, searchKey: searchKey }; return $http({ url: 'resource/machineResource.json', params: param, method: 'GET' }); }; this.fetchClusterNodeOfMachine = function (ip, port, searchKey) { var param = { ip: ip, port: port, type: 'cluster', searchKey: searchKey }; return $http({ url: 'resource/machineResource.json', params: param, method: 'GET' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/machineservice.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('MachineService', ['$http', '$httpParamSerializerJQLike', function ($http, $httpParamSerializerJQLike) { this.getAppMachines = function (app) { return $http({ url: 'app/' + app + '/machines.json', method: 'GET' }); }; this.removeAppMachine = function (app, ip, port) { return $http({ url: 'app/' + app + '/machine/remove.json', method: 'POST', headers: { 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, data: $httpParamSerializerJQLike({ ip: ip, port: port }) }); }; }] ); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/metricservice.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('MetricService', ['$http', function ($http) { this.queryAppSortedIdentities = function (params) { return $http({ url: '/metric/queryTopResourceMetric.json', params: params, method: 'GET' }); }; this.queryByAppAndIdentity = function (params) { return $http({ url: '/metric/queryByAppAndResource.json', params: params, method: 'GET' }); }; this.queryByMachineAndIdentity = function (ip, port, identity, startTime, endTime) { var param = { ip: ip, port: port, identity: identity, startTime: startTime.getTime(), endTime: endTime.getTime() }; return $http({ url: '/metric/queryByAppAndResource.json', params: param, method: 'GET' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/param_flow_service.js ================================================ /** * Parameter flow control service. * * @author Eric Zhao */ angular.module('sentinelDashboardApp').service('ParamFlowService', ['$http', function ($http) { this.queryMachineRules = function(app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: '/paramFlow/rules', params: param, method: 'GET' }); }; this.addNewRule = function(rule) { return $http({ url: '/paramFlow/rule', data: rule, method: 'POST' }); }; this.saveRule = function (entity) { return $http({ url: '/paramFlow/rule/' + entity.id, data: entity, method: 'PUT' }); }; this.deleteRule = function (entity) { return $http({ url: '/paramFlow/rule/' + entity.id, method: 'DELETE' }); }; function isNumberClass(classType) { return classType === 'int' || classType === 'double' || classType === 'float' || classType === 'long' || classType === 'short'; } function isByteClass(classType) { return classType === 'byte'; } function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; } function notGoodNumber(num) { return num === undefined || num === '' || isNaN(num); } function notGoodNumberBetweenExclusive(num, l ,r) { return num === undefined || num === '' || isNaN(num) || num < l || num > r; } function notValidParamItem(curExItem) { if (isNumberClass(curExItem.classType) && notGoodNumber(curExItem.object)) { return true; } if (isByteClass(curExItem.classType) && notGoodNumberBetweenExclusive(curExItem.object, -128, 127)) { return true; } return curExItem.object === undefined || curExItem.classType === undefined || notNumberAtLeastZero(curExItem.count); } this.checkRuleValid = function (rule) { if (!rule.resource || rule.resource === '') { alert('资源名称不能为空'); return false; } if (rule.grade != 1) { alert('未知的限流模式'); return false; } if (rule.count < 0) { alert('限流阈值必须大于等于 0'); return false; } if (rule.paramIdx === undefined || rule.paramIdx === '' || isNaN(rule.paramIdx) || rule.paramIdx < 0) { alert('热点参数索引必须大于等于 0'); return false; } if (rule.paramFlowItemList !== undefined) { for (var i = 0; i < rule.paramFlowItemList.length; i++) { var item = rule.paramFlowItemList[i]; if (notValidParamItem(item)) { alert('热点参数例外项不合法,请检查值和类型是否正确:参数为 ' + item.object + ', 类型为 ' + item.classType + ', 限流阈值为 ' + item.count); return false; } } } return true; }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/systemservice.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('SystemService', ['$http', function ($http) { this.queryMachineRules = function (app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ url: 'system/rules.json', params: param, method: 'GET' }); }; this.newRule = function (rule) { var param = { app: rule.app, ip: rule.ip, port: rule.port }; if (rule.grade == 0) {// avgLoad param.highestSystemLoad = rule.highestSystemLoad; } else if (rule.grade == 1) {// avgRt param.avgRt = rule.avgRt; } else if (rule.grade == 2) {// maxThread param.maxThread = rule.maxThread; } else if (rule.grade == 3) {// qps param.qps = rule.qps; } else if (rule.grade == 4) {// cpu param.highestCpuUsage = rule.highestCpuUsage; } return $http({ url: '/system/new.json', params: param, method: 'GET' }); }; this.saveRule = function (rule) { var param = { id: rule.id, }; if (rule.grade == 0) {// avgLoad param.highestSystemLoad = rule.highestSystemLoad; } else if (rule.grade == 1) {// avgRt param.avgRt = rule.avgRt; } else if (rule.grade == 2) {// maxThread param.maxThread = rule.maxThread; } else if (rule.grade == 3) {// qps param.qps = rule.qps; } else if (rule.grade == 4) {// cpu param.highestCpuUsage = rule.highestCpuUsage; } return $http({ url: '/system/save.json', params: param, method: 'GET' }); }; this.deleteRule = function (rule) { var param = { id: rule.id, app: rule.app }; return $http({ url: '/system/delete.json', params: param, method: 'GET' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/scripts/services/version_service.js ================================================ var app = angular.module('sentinelDashboardApp'); app.service('VersionService', ['$http', function ($http) { this.version = function () { return $http({ url: '/version', method: 'GET' }); }; }]); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/styles/main.css ================================================ .browsehappy { margin: 0.2em 0; background: #ccc; color: #000; padding: 0.2em 0; } body { padding: 0; } /* Everything but the jumbotron gets side spacing for mobile first views */ .header, .marketing, .footer { padding-left: 15px; padding-right: 15px; } /* Custom page header */ .header { border-bottom: 1px solid #e5e5e5; margin-bottom: 10px; } /* Make the masthead heading the same height as the navigation */ .header h3 { margin-top: 0; margin-bottom: 0; line-height: 40px; padding-bottom: 19px; } /* Custom page footer */ .footer { padding-top: 19px; color: #777; border-top: 1px solid #e5e5e5; } .container-narrow > hr { margin: 30px 0; } /* Main marketing message and sign up button */ .jumbotron { text-align: center; border-bottom: 1px solid #e5e5e5; } .jumbotron .btn { font-size: 21px; padding: 14px 24px; } /* Supporting marketing content */ .marketing { margin: 40px 0; } .marketing p + h4 { margin-top: 28px; } /* Responsive: Portrait tablets and up */ @media screen and (min-width: 768px) { .container { width: inherit; margin-left: 60px; margin-right: 5px; } /* Remove the padding we set earlier */ .header, .marketing, .footer { padding-left: 0; padding-right: 0; } /* Space out the masthead */ .header { margin-bottom: 30px; } /* Remove the bottom border on the jumbotron for visual effect */ .jumbotron { border-bottom: 0; } } .navbar-inverse { background-color: #1d9d74; border-color: #1b926c; } .navbar-inverse .navbar-nav > li > a { color: #b0ddce; font-size: 15px; } .navbar-inverse .navbar-nav>.open>a, .navbar-inverse .navbar-nav>.open>a:focus, .navbar-inverse .navbar-nav>.open>a:hover { background-color: #1b926c; } @media (min-width: 900px) { .navbar-left { float: left !important; } .navbar-right { float: right !important; margin-right: 0%; } .navbar-right ~ .navbar-right { margin-right: 0; } } .dropdown-menu { min-width: 100px !important; } .nav-sidebar li.active a { background: #DDD; } .dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus { background: #1d9d74; /*background: #d9d9d9;*/ color: white; } .broadcast-message, .broadcast-message-preview { padding: 10px; text-align: center; background: #555; color: #BBB; margin-top: 50px; } .card { position: relative; border: 1px solid #d9d9d9; border-radius: 3px; color: #666; background-color: #fff; width: 100%; border-radius: 5px; } .card .card-header { padding: 9px 0; height: 40px; background: #555; color: #fff; text-align: center; border-top-left-radius: 4px; border-top-right-radius: 4px; } .card .card-body { padding: 12px 10px; } .card .card-footer { height: 20px; font-size: 10px; color: #777; margin-top: -15px; margin-bottom: 5px; margin-left: 20px; margin-right: 20px; } .card .detail-brand { float: left; width: 30%; line-height: 98px; font-size: 30px; text-align: center; color: white; } .card .default { background: #1d9d74; } .card .info { background: #6EBEE7; } .card .warn { background: #ED7F54; } .card .danger { background: #6583BE; } .card .detail .text-default { color: #1d9d74; } .card .detail .text-info { color: #6EBEE7; } .card .detail .text-warn { color: #ED7F54; } .card .detail .text-danger { color: #6583BE; } .card .detail { float: right; width: 70%; line-height: 98px; text-align: center; } .card .detail .text { font-size: 12px; } .card .detail .number { font-size: 30px; font-weight: 500; } .h100 { height: 100px; } .inline { display: inline; } .separator { height: 1px; background-color: #e5e5e5; margin-top: 10px; } .card > .card-body > table > thead > tr > td, .card > .card-body > table > tbody > tr > td { word-wrap: break-word; word-break: break-all; } .card > .card-body > table > thead > tr > td { font-weight: 500; font-size: 13px; text-align: center; } .card > .card-body > table > thead > tr > td > span { font-weight: 500; font-size: 10px; } .card > .card-body > table > tbody > tr > td { font-size: 12px; text-align: center; } .card > .card-body > table > tbody > tr > td > a { color: #666; } .thumbnails > .card > .card-body > table > thead > tr > td, .thumbnails > .card > .card-body > table > tbody > tr > td { font-size: 12px; color: #777; word-wrap: break-word; word-break: break-all; } .thumbnails > .card > .card-body > table > thead > tr > td:nth-child(n+2) { text-align: center; } .thumbnails > .card > .card-body > table > tbody > tr > td:nth-child(n+2) { font-weight: 700; text-align: center; } .thumbnails > .card > .card-body > table > thead > tr > td:nth-child(1), .thumbnails > .card > .card-body > table > tbody > tr > td:nth-child(1) { text-align: left; } .tools-header { background: whitesmoke; padding: 9px 0; height: 40px; border-top-left-radius: 4px; border-top-right-radius: 4px; } .tools-header .brand { font-size: 13px; margin: 2px 10px; font-weight: 700; float: left; } .tools-header .brand > a { color: #666; } .tools-header > button, .tools-header > select, .tools-header > a { float: right; max-width: 80px; margin: 1px 10px; height: 25px; padding: 0 10px; line-height: 25px; color: #666; } .tools-header .paged { margin-right: 0px; } .btn { height: 32px; } .btn.btn-main { color: #ffffff; background-color: #337ab7; border-color: #337ab7; } .btn:focus, .btn:active { outline: none !important; } .btn-default:hover, .btn-default:focus, .btn-default:active { color: #1d9d74; border-color: #1d9d74; background: white; } .btn.btn-danger-tag { color: #ffffff; background-color: #d9534f; border-color: #d43f3a; line-height: 1px; font-size: 11px; padding: 4px 4px; } .btn.btn-danger { color: #333; background-color: #fff; border-color: #ccc; } .btn.btn-danger:hover, .btn.btn-danger:focus, .btn.btn-danger:active { color: #d9534f; border-color: #d9534f; background: white; } .form-control { height : 32px; } .form-control:focus { border-color: #337ab7; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); } .form-control { border-radius: 8px; } .input-label:before { display: inline-block; content: "*"; color: #f44336; font-family: SimSun; font-size: 12px; -webkit-transform: TranslateX(-10px); -ms-transform: TranslateX(-10px); transform: TranslateX(-10px); } .label.label-main { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .badge-main { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .bootstrap-tagsinput { background-color: #fff; border: 1px solid #ccc; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); display: inline-block; padding: 4px 6px; color: #555; vertical-align: middle; border-radius: 4px; /* max-width: 100%; */ width: 85%; height: 100px; line-height: 20px; cursor: text; } .bootstrap-tagsinput > .dropdown-menu { min-width: 40px; font-size: 12px; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover { background-color: #1d9d74; background-image: -webkit-linear-gradient(top, #1d9d74 0, #1d9d74 100%); background-image: -o-linear-gradient(top, #1d9d74 0, #1d9d74 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#1d9d74), to(#1d9d74)); background-image: linear-gradient(to bottom, #1d9d74 0, #1d9d74 100%); filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#1d9d74', endColorstr='#1d9d74', GradientType=0); background-repeat: repeat-x; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover { color: #fff; text-decoration: none; background-color: #1d9d74; outline: 0; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus { color: white; text-decoration: none; outline: 0; background-color: #1d9d74; } .inputs-header { padding: 9px 0; height: 50px; border-top-left-radius: 4px; border-top-right-radius: 4px; } .inputs-header .brand { font-size: 13px; margin: 2px 10px; font-weight: 700; float: left; } .inputs-header .brand > a { color: #666; } .inputs-header > input { float: right; margin: 1px 10px; height: 30px; padding: 0 10px; color: #666; } .inputs-header > a { float: right; margin: 1px 10px; height: 30px; padding: 5 5px; } .inputs-header > select { float: right; max-width: 80px; margin: 1px 10px; height: 30px; padding: 0 10px; color: #666; height: 25px; font-size: 12px; } .witdh-150 { max-width: 150px; } .witdh-200 { max-width: 200px; } .width-200 { max-width: 200px; } .witdh-300 { max-width: 300px; } .width-300 { max-width: 300px; } .card.highlight { border-color: #d9534f; } .card .pagination-footer { height: 40px; font-size: 10px; color: #777; margin-top: -15px; margin-bottom: 5px; margin-left: 20px; margin-right: 20px; } .card .pagination-footer .tools { font-size: 12px; margin: 11px 0; float: right; display: inline; margin-right: 20px; } .card > .pagination-footer > .tools > span > input { height: 25px; max-width: 50px; display: inline; } .pagination { display: inline-block; padding-left: 0; margin: 8px 0; float: right; border-radius: 4px; } .pagination > a { margin-right: 5px; height: 28px; width: 28px; padding: 5px 0px; } .pagination > .btn.active { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .datepicker > .table > thead > tr > td, .datepicker > .table > tbody > tr > td, .timepicker > .table > thead > tr > td, .timepicker > .table > tbody > tr > td { padding: 5px 3px; } .datepicker > .table > thead > tr > td > .btn, .datepicker > .table > tbody > tr > td > .btn, .timepicker > .table > thead > tr > td > .btn, .timepicker > .table > tbody > tr > td > .btn { border: 1px solid #FFFDFD; } .datepicker > .table > thead > tr > td > .btn-default:hover, .datepicker > .table > thead > tr > td > .btn-default:focus, .datepicker > .table > thead > tr > td > .btn-default:active, .datepicker > .table > tbody > tr > td > .btn-default:hover, .datepicker > .table > tbody > tr > td > .btn-default:focus, .datepicker > .table > tbody > tr > td > .btn-default:active, .timepicker > .table > thead > tr > td > .btn-default:hover, .timepicker > .table > thead > tr > td > .btn-default:focus, .timepicker > .table > thead > tr > td > .btn-default:active, .timepicker > .table > tbody > tr > td > .btn-default:hover, .timepicker > .table > tbody > tr > td > .btn-default:focus, .timepicker > .table > tbody > tr > td > .btn-default:active { color: #1d9d74; border-color: #1d9d74; background: white; } .datepicker > .table > thead > tr > td > a, .datepicker > .table > tbody > tr > td > a, .timepicker > .table > thead > tr > td > a, .timepicker > .table > tbody > tr > td > a { height: 25px; width: 25px; padding: 3px 0px; } .datepicker > .table > tbody > tr:first-child > td > a { padding: 4px 0px; } .datepicker > .table > thead > tr > td > a.btn.active, .datepicker > .table > tbody > tr > td > a.btn.active, .timepicker > .table > thead > tr > td > a.btn.active, .timepicker > .table > tbody > tr > td > a.btn.active { /* color: #ffffff; background-color: #1d9d74; border-color: #1d9d74;*/ color: #1d9d74; border-color: #1d9d74; background: white; box-shadow: inset 0 0px 0px rgba(0,0,0,0.125); } .datepicker > .table > thead > tr > td:not(:first-child):last-child > a, .timepicker > .table > thead > tr > td:not(:first-child):last-child > a { height: 25px; width: 50px; padding: 5px 0px; } .datepicker > .table > tbody > tr > td > a, .timepicker > .table > tbody > tr > td > a { margin-left: 8px; } .selectize-input-200 > .selectize-input { min-width: 250px; } .highlight-border { border-color: #337ab7; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); }.browsehappy { margin: 0.2em 0; background: #ccc; color: #000; padding: 0.2em 0; } body { padding: 0; } /* Everything but the jumbotron gets side spacing for mobile first views */ .header, .marketing, .footer { padding-left: 15px; padding-right: 15px; } /* Custom page header */ .header { border-bottom: 1px solid #e5e5e5; margin-bottom: 10px; } /* Make the masthead heading the same height as the navigation */ .header h3 { margin-top: 0; margin-bottom: 0; line-height: 40px; padding-bottom: 19px; } /* Custom page footer */ .footer { padding-top: 19px; color: #777; border-top: 1px solid #e5e5e5; } .container-narrow > hr { margin: 30px 0; } /* Main marketing message and sign up button */ .jumbotron { text-align: center; border-bottom: 1px solid #e5e5e5; } .jumbotron .btn { font-size: 21px; padding: 14px 24px; } /* Supporting marketing content */ .marketing { margin: 40px 0; } .marketing p + h4 { margin-top: 28px; } /* Responsive: Portrait tablets and up */ @media screen and (min-width: 768px) { .container { width: inherit; margin-left: 60px; margin-right: 5px; } /* Remove the padding we set earlier */ .header, .marketing, .footer { padding-left: 0; padding-right: 0; } /* Space out the masthead */ .header { margin-bottom: 30px; } /* Remove the bottom border on the jumbotron for visual effect */ .jumbotron { border-bottom: 0; } } .navbar-inverse { background-color: #1d9d74; border-color: #1b926c; } .navbar-inverse .navbar-nav > li > a { color: #b0ddce; font-size: 15px; } .navbar-inverse .navbar-nav>.open>a, .navbar-inverse .navbar-nav>.open>a:focus, .navbar-inverse .navbar-nav>.open>a:hover { background-color: #1b926c; } @media (min-width: 900px) { .navbar-left { float: left !important; } .navbar-right { float: right !important; margin-right: 0%; } .navbar-right ~ .navbar-right { margin-right: 0; } } .dropdown-menu { min-width: 100px !important; } .nav-sidebar li.active a { background: #DDD; } .dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus { background: #1d9d74; /*background: #d9d9d9;*/ color: white; } .broadcast-message, .broadcast-message-preview { padding: 10px; text-align: center; background: #555; color: #BBB; margin-top: 50px; } .card { position: relative; border: 1px solid #d9d9d9; border-radius: 3px; color: #666; background-color: #fff; width: 100%; border-radius: 5px; } .card .card-header { padding: 9px 0; height: 40px; background: #555; color: #fff; text-align: center; border-top-left-radius: 4px; border-top-right-radius: 4px; } .card .card-body { padding: 12px 10px; } .card .card-footer { height: 20px; font-size: 10px; color: #777; margin-top: -15px; margin-bottom: 5px; margin-left: 20px; margin-right: 20px; } .card .detail-brand { float: left; width: 30%; line-height: 98px; font-size: 30px; text-align: center; color: white; } .card .default { background: #1d9d74; } .card .info { background: #6EBEE7; } .card .warn { background: #ED7F54; } .card .danger { background: #6583BE; } .card .detail .text-default { color: #1d9d74; } .card .detail .text-info { color: #6EBEE7; } .card .detail .text-warn { color: #ED7F54; } .card .detail .text-danger { color: #6583BE; } .card .detail { float: right; width: 70%; line-height: 98px; text-align: center; } .card .detail .text { font-size: 12px; } .card .detail .number { font-size: 30px; font-weight: 500; } .h100 { height: 100px; } .inline { display: inline; } .separator { height: 1px; background-color: #e5e5e5; margin-top: 10px; } .card > .card-body > table > thead > tr > td, .card > .card-body > table > tbody > tr > td { word-wrap: break-word; word-break: break-all; } .card > .card-body > table > thead > tr > td { font-weight: 500; font-size: 13px; text-align: center; } .card > .card-body > table > thead > tr > td > span { font-weight: 500; font-size: 10px; } .card > .card-body > table > tbody > tr > td { font-size: 12px; text-align: center; } .card > .card-body > table > tbody > tr > td > a { color: #666; } .thumbnails > .card > .card-body > table > thead > tr > td, .thumbnails > .card > .card-body > table > tbody > tr > td { font-size: 12px; color: #777; word-wrap: break-word; word-break: break-all; } .thumbnails > .card > .card-body > table > thead > tr > td:nth-child(n+2) { text-align: center; } .thumbnails > .card > .card-body > table > tbody > tr > td:nth-child(n+2) { font-weight: 700; text-align: center; } .thumbnails > .card > .card-body > table > thead > tr > td:nth-child(1), .thumbnails > .card > .card-body > table > tbody > tr > td:nth-child(1) { text-align: left; } .tools-header { background: whitesmoke; padding: 9px 0; height: 40px; border-top-left-radius: 4px; border-top-right-radius: 4px; } .tools-header .brand { font-size: 13px; margin: 2px 10px; font-weight: 700; float: left; } .tools-header .brand > a { color: #666; } .tools-header > button, .tools-header > select, .tools-header > a { float: right; max-width: 80px; margin: 1px 10px; height: 25px; padding: 0 10px; line-height: 25px; color: #666; } .tools-header .paged { margin-right: 0px; } .btn.btn-main { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .btn:focus, .btn:active { outline: none !important; } .btn-default:hover, .btn-default:focus, .btn-default:active { color: #1d9d74; border-color: #1d9d74; background: white; } .btn-default-inverse { color: #1d9d74; border-color: #1d9d74; background: white; } .btn-default-inverse:hover, .btn-default-inverse:focus, .btn-default:active { color: #1d9d74; border-color: #1d9d74; background: white; } .btn.btn-danger-tag { color: #ffffff; background-color: #d9534f; border-color: #d43f3a; line-height: 1px; font-size: 11px; padding: 4px 4px; } .btn.btn-danger { color: #333; background-color: #fff; border-color: #ccc; } .btn.btn-danger:hover, .btn.btn-danger:focus, .btn.btn-danger:active { color: #d9534f; border-color: #d9534f; background: white; } .form-control { height : 32px; } .form-control:focus { border-color: #1d9d74; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); } .form-control { border-radius: 8px; } .input-label:before { display: inline-block; content: "*"; color: #f44336; font-family: SimSun; font-size: 12px; -webkit-transform: TranslateX(-10px); -ms-transform: TranslateX(-10px); transform: TranslateX(-10px); } .label.label-main { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .badge-main { color: #ffffff; background-color: #1d9d74; border-color: #1d9d74; } .bootstrap-tagsinput { background-color: #fff; border: 1px solid #ccc; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); display: inline-block; padding: 4px 6px; color: #555; vertical-align: middle; border-radius: 4px; /* max-width: 100%; */ width: 85%; height: 100px; line-height: 20px; cursor: text; } .bootstrap-tagsinput > .dropdown-menu { min-width: 40px; font-size: 12px; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover { background-color: #1d9d74; background-image: -webkit-linear-gradient(top, #1d9d74 0, #1d9d74 100%); background-image: -o-linear-gradient(top, #1d9d74 0, #1d9d74 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#1d9d74), to(#1d9d74)); background-image: linear-gradient(to bottom, #1d9d74 0, #1d9d74 100%); filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#1d9d74', endColorstr='#1d9d74', GradientType=0); background-repeat: repeat-x; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover { color: #fff; text-decoration: none; background-color: #1d9d74; outline: 0; } .bootstrap-tagsinput > .dropdown-menu>.active>a, .bootstrap-tagsinput > .dropdown-menu>.active>a:hover, .bootstrap-tagsinput > .dropdown-menu>.active>a:focus { color: white; text-decoration: none; outline: 0; background-color: #1d9d74; } .inputs-header { padding: 9px 0; height: 50px; border-top-left-radius: 4px; border-top-right-radius: 4px; } .inputs-header .brand { font-size: 13px; margin: 2px 10px; font-weight: 700; float: left; } .inputs-header .brand > a { color: #666; } .inputs-header > input { float: right; margin: 1px 10px; height: 30px; padding: 0 10px; color: #666; } .inputs-header > a { float: right; margin: 1px 10px; height: 30px; padding: 5 5px; } .inputs-header > select { float: right; max-width: 80px; margin: 1px 10px; height: 30px; padding: 0 10px; color: #666; height: 25px; font-size: 12px; } .witdh-150 { max-width: 150px; } .witdh-200 { max-width: 200px; } .card.highlight { border-color: #d9534f; } .card .pagination-footer { height: 40px; font-size: 10px; color: #777; margin-top: -15px; margin-bottom: 5px; margin-left: 20px; margin-right: 20px; } .card .pagination-footer .tools { font-size: 12px; margin: 11px 0; float: right; display: inline; margin-right: 20px; } .card > .pagination-footer > .tools > span > input { height: 25px; max-width: 50px; display: inline; } .pagination { display: inline-block; padding-left: 0; margin: 8px 0; float: right; border-radius: 4px; } .pagination > a { margin-right: 5px; height: 28px; width: 28px; padding: 5px 0px; } .pagination > .btn.active { color: #ffffff; background-color: #449d44; border-color: #449d44; } .datepicker > .table > thead > tr > td, .datepicker > .table > tbody > tr > td, .timepicker > .table > thead > tr > td, .timepicker > .table > tbody > tr > td { padding: 5px 3px; } .datepicker > .table > thead > tr > td > .btn, .datepicker > .table > tbody > tr > td > .btn, .timepicker > .table > thead > tr > td > .btn, .timepicker > .table > tbody > tr > td > .btn { border: 1px solid #FFFDFD; } .datepicker > .table > thead > tr > td > .btn-default:hover, .datepicker > .table > thead > tr > td > .btn-default:focus, .datepicker > .table > thead > tr > td > .btn-default:active, .datepicker > .table > tbody > tr > td > .btn-default:hover, .datepicker > .table > tbody > tr > td > .btn-default:focus, .datepicker > .table > tbody > tr > td > .btn-default:active, .timepicker > .table > thead > tr > td > .btn-default:hover, .timepicker > .table > thead > tr > td > .btn-default:focus, .timepicker > .table > thead > tr > td > .btn-default:active, .timepicker > .table > tbody > tr > td > .btn-default:hover, .timepicker > .table > tbody > tr > td > .btn-default:focus, .timepicker > .table > tbody > tr > td > .btn-default:active { color: #1d9d74; border-color: #1d9d74; background: white; } .datepicker > .table > thead > tr > td > a, .datepicker > .table > tbody > tr > td > a, .timepicker > .table > thead > tr > td > a, .timepicker > .table > tbody > tr > td > a { height: 25px; width: 25px; padding: 3px 0px; } .datepicker > .table > tbody > tr:first-child > td > a { padding: 4px 0px; } .datepicker > .table > thead > tr > td > a.btn.active, .datepicker > .table > tbody > tr > td > a.btn.active, .timepicker > .table > thead > tr > td > a.btn.active, .timepicker > .table > tbody > tr > td > a.btn.active { /* color: #ffffff; background-color: #1d9d74; border-color: #1d9d74;*/ color: #1d9d74; border-color: #1d9d74; background: white; box-shadow: inset 0 0px 0px rgba(0,0,0,0.125); } .datepicker > .table > thead > tr > td:not(:first-child):last-child > a, .timepicker > .table > thead > tr > td:not(:first-child):last-child > a { height: 25px; width: 50px; padding: 5px 0px; } .datepicker > .table > tbody > tr > td > a, .timepicker > .table > tbody > tr > td > a { margin-left: 8px; } .selectize-input-200 > .selectize-input { min-width: 250px; } .highlight-border { border-color: #1d9d74; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); } .sortorder:after { content: '\25b2'; } .sortorder.reverse:after { content: '\25bc'; } .input-control select { -moz-appearance: none; -webkit-appearance: none; appearance: none; position: relative; border: 1px #d9d9d9 solid; width: 100%; height: 100%; padding: .3125rem; z-index: 0; } .navbar-inverse { background-color: #337ab7; border-color: #337ab7; } .sidebar { z-index: 1; width: 220px; /*position: fixed;*/ top: 0; left: 0; height: 100%; } #page-wrapper { position: inherit; margin: 70px 0 0 220px; padding: 12px 30px; border-left: 0px solid #e7e7e7; } .sidebar .sidebar-nav.navbar-collapse { padding-right: 0; padding-left: 0; background-color: #F5F5F5; position: relative; color: black; width: 100%; padding: 0; margin: 0; list-style: none inside none; } .sidebar a { color: #555; } .sidebar ul li:hover { color:red; } .form-control { border-radius: 8px; } .form-control:focus { border-color: #337ab7; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); } .highlight-border { border-color: #337ab7; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(29, 157, 116, 1); }.browsehappy { margin: 0.2em 0; background: #ccc; color: #000; padding: 0.2em 0; } .btn.btn-main { color: #ffffff; background-color: #337ab7; border-color: #337ab7; } .btn-default-inverse { color: #337ab7; border-color: #337ab7; background: white; } .btn-default-inverse:hover, .btn-default-inverse:focus, .btn-default:active { color: #337ab7; border-color: #337ab7; background: white; } .btn-danger-inverse { color: #d9534f; border-color: #d9534f; background: white; } .btn-danger-inverse:hover, .btn-danger-inverse:focus, .btn-danger:active { color: #d9534f; border-color: #d9534f; background: white; } .btn-tab-active, .btn-tab-active:hover, .btn-tab-active:focus, .btn-tab-default:hover, .btn-tab-default:focus, .btn-tab-default:active { color: #337ab7; border-color: #337ab7; background: white; font-weight: 600; } .btn-tab-default { color: #777; background: white; font-weight: 600; } .pagination > .btn.active { color: #ffffff; background-color: #337ab7; border-color: #337ab7; } .btn-default:hover, .btn-default:focus, .btn-default:active { color: #337ab7; border-color: #337ab7; background: white; } .bootstrap-switch.bootstrap-switch-on { border-color: #337ab7; } .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { color: #fff; background: #337ab7; } .selectize-input-200 > .selectize-input { min-width: 200px; border-color: #337ab7; } .btn-outline-primary { color: #007bff; background-color: transparent; background-image: none; border-color: #007bff; } .btn-outline-primary:hover { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:focus, .btn-outline-primary.focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-primary.disabled, .btn-outline-primary:disabled { color: #007bff; background-color: transparent; } .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-secondary { color: #6c757d; background-color: transparent; background-image: none; border-color: #6c757d; } .btn-outline-secondary:hover { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:focus, .btn-outline-secondary.focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { color: #6c757d; background-color: transparent; } .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-success { color: #28a745; background-color: transparent; background-image: none; border-color: #28a745; } .btn-outline-success:hover { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:focus, .btn-outline-success.focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-success.disabled, .btn-outline-success:disabled { color: #28a745; background-color: transparent; } .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-info { color: #17a2b8; background-color: transparent; background-image: none; border-color: #17a2b8; } .btn-outline-info:hover { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:focus, .btn-outline-info.focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-info.disabled, .btn-outline-info:disabled { color: #17a2b8; background-color: transparent; } .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-warning { color: #ffc107; background-color: transparent; background-image: none; border-color: #ffc107; } .btn-outline-warning:hover { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:focus, .btn-outline-warning.focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-warning.disabled, .btn-outline-warning:disabled { color: #ffc107; background-color: transparent; } .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-danger { color: #dc3545; background-color: transparent; background-image: none; border-color: #dc3545; } .btn-outline-danger:hover { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:focus, .btn-outline-danger.focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-danger.disabled, .btn-outline-danger:disabled { color: #dc3545; background-color: transparent; } .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-light { color: #f8f9fa; background-color: transparent; background-image: none; border-color: #f8f9fa; } .btn-outline-light:hover { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:focus, .btn-outline-light.focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-light.disabled, .btn-outline-light:disabled { color: #f8f9fa; background-color: transparent; } .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-dark { color: #343a40; background-color: transparent; background-image: none; border-color: #343a40; } .btn-outline-dark:hover { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:focus, .btn-outline-dark.focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .btn-outline-dark.disabled, .btn-outline-dark:disabled { color: #343a40; background-color: transparent; } .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/styles/page.css ================================================ /*! * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) * Code licensed under the Apache License v2.0. * For details, see http://www.apache.org/licenses/LICENSE-2.0. */ body { background-color: #f8f8f8; } .example { padding: .625rem 1.825rem .625rem 2.5rem; border: 1px #ccc dashed; position: relative; margin: 0 0 .625rem 0; background-color: #ffffff; } dl dt, dl dd { line-height: 1.25rem; } dl dt { font-style: normal; font-weight: 700; } dl dd { margin-left: .9375rem; } dl.horizontal dt { float: left; width: 10rem; overflow: hidden; clear: left; text-align: right; text-overflow: ellipsis; white-space: nowrap; } dl.horizontal dd { margin-left: 11.25rem; } #wrapper { width: 100%; } #page-wrapper { padding: 0 15px; min-height: 568px; background-color: #fff; } @media(min-width:768px) { #page-wrapper { position: inherit; margin: 0 0 0 250px; padding: 0 30px; border-left: 1px solid #e7e7e7; } } .navbar-top-links { margin-right: 0; } .navbar-top-links li { display: inline-block; } .navbar-top-links li:last-child { margin-right: 15px; } .navbar-top-links li a { padding: 15px; min-height: 50px; } .navbar-top-links .dropdown-menu li { display: block; } .navbar-top-links .dropdown-menu li:last-child { margin-right: 0; } .navbar-top-links .dropdown-menu li a { padding: 3px 20px; min-height: 0; } .navbar-top-links .dropdown-menu li a div { white-space: normal; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { width: 310px; min-width: 0; } .navbar-top-links .dropdown-messages { margin-left: 5px; } .navbar-top-links .dropdown-tasks { margin-left: -59px; } .navbar-top-links .dropdown-alerts { margin-left: -123px; } .navbar-top-links .dropdown-user { right: 0; left: auto; } .sidebar .sidebar-nav.navbar-collapse { padding-right: 0; padding-left: 0; background-color: #71b1d1; color: #ffffff; position: relative; width: 100%; padding: 0; margin: 0; list-style: none inside none; } .sidebar .sidebar-search { padding: 15px; } .sidebar ul li { border-bottom: 1px solid #e7e7e7; } .sidebar ul li a.active { background-color: #ffffff; color: #ffffff; } .sidebar a{ color: #fff; } .sidebar .arrow { float: right; } .sidebar .fa.arrow:before { content: "\f104"; } .sidebar .active>a>.fa.arrow:before { content: "\f107"; } .sidebar .nav-second-level li, .sidebar .nav-third-level li { border-bottom: 0!important; } .sidebar .nav-second-level li a { padding-left: 37px; } .sidebar .nav-third-level li a { padding-left: 52px; } @media(min-width:768px) { .sidebar { z-index: 1; position: absolute; width: 250px; margin-top: 51px; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { margin-left: auto; } } .btn-outline { color: inherit; background-color: transparent; transition: all .5s; } .btn-primary.btn-outline { color: #428bca; } .btn-success.btn-outline { color: #5cb85c; } .btn-info.btn-outline { color: #5bc0de; } .btn-warning.btn-outline { color: #f0ad4e; } .btn-danger.btn-outline { color: #d9534f; } .btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover { color: #fff; } .chat { margin: 0; padding: 0; list-style: none; } .chat li { margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px dotted #999; } .chat li.left .chat-body { margin-left: 60px; } .chat li.right .chat-body { margin-right: 60px; } .chat li .chat-body p { margin: 0; } .panel .slidedown .glyphicon, .chat .glyphicon { margin-right: 5px; } .chat-panel .panel-body { height: 350px; overflow-y: scroll; } .login-panel { margin-top: 25%; } .flot-chart { display: block; height: 400px; } .flot-chart-content { width: 100%; height: 100%; } .dataTables_wrapper { position: relative; clear: both; } table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTable thead .sorting_desc, table.dataTable thead .sorting_asc_disabled, table.dataTable thead .sorting_desc_disabled { background: 0 0; } table.dataTable thead .sorting_asc:after { content: "\f0de"; float: right; font-family: fontawesome; } table.dataTable thead .sorting_desc:after { content: "\f0dd"; float: right; font-family: fontawesome; } table.dataTable thead .sorting:after { content: "\f0dc"; float: right; font-family: fontawesome; color: rgba(50,50,50,.5); } .btn-circle { width: 30px; height: 30px; padding: 6px 0; border-radius: 15px; text-align: center; font-size: 12px; line-height: 1.428571429; } .btn-circle.btn-lg { width: 50px; height: 50px; padding: 10px 16px; border-radius: 25px; font-size: 18px; line-height: 1.33; } .btn-circle.btn-xl { width: 70px; height: 70px; padding: 10px 16px; border-radius: 35px; font-size: 24px; line-height: 1.33; } .show-grid [class^=col-] { padding-top: 10px; padding-bottom: 10px; border: 1px solid #ddd; background-color: #eee!important; } .show-grid { margin: 15px 0; } .huge { font-size: 40px; } .panel-green { border-color: #5cb85c; } .panel-green .panel-heading { border-color: #5cb85c; color: #fff; background-color: #5cb85c; } .panel-green a { color: #5cb85c; } .panel-green a:hover { color: #3d8b3d; } .panel-red { border-color: #d9534f; } .panel-red .panel-heading { border-color: #d9534f; color: #fff; background-color: #d9534f; } .panel-red a { color: #d9534f; } .panel-red a:hover { color: #b52b27; } .panel-yellow { border-color: #f0ad4e; } .panel-yellow .panel-heading { border-color: #f0ad4e; color: #fff; background-color: #f0ad4e; } .panel-yellow a { color: #f0ad4e; } .panel-yellow a:hover { color: #df8a13; } ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/styles/timeline.css ================================================ .timeline { position: relative; padding: 20px 0 20px; list-style: none; } .timeline:before { content: " "; position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; margin-left: -1.5px; background-color: #eeeeee; } .timeline > li { position: relative; margin-bottom: 20px; } .timeline > li:before, .timeline > li:after { content: " "; display: table; } .timeline > li:after { clear: both; } .timeline > li:before, .timeline > li:after { content: " "; display: table; } .timeline > li:after { clear: both; } .timeline > li > .timeline-panel { float: left; position: relative; width: 46%; padding: 20px; border: 1px solid #d4d4d4; border-radius: 2px; -webkit-box-shadow: 0 1px 6px rgba(0,0,0,0.175); box-shadow: 0 1px 6px rgba(0,0,0,0.175); } .timeline > li > .timeline-panel:before { content: " "; display: inline-block; position: absolute; top: 26px; right: -15px; border-top: 15px solid transparent; border-right: 0 solid #ccc; border-bottom: 15px solid transparent; border-left: 15px solid #ccc; } .timeline > li > .timeline-panel:after { content: " "; display: inline-block; position: absolute; top: 27px; right: -14px; border-top: 14px solid transparent; border-right: 0 solid #fff; border-bottom: 14px solid transparent; border-left: 14px solid #fff; } .timeline > li > .timeline-badge { z-index: 100; position: absolute; top: 16px; left: 50%; width: 50px; height: 50px; margin-left: -25px; border-radius: 50% 50% 50% 50%; text-align: center; font-size: 1.4em; line-height: 50px; color: #fff; background-color: #999999; } .timeline > li.timeline-inverted > .timeline-panel { float: right; } .timeline > li.timeline-inverted > .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } .timeline > li.timeline-inverted > .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } .timeline-badge.primary { background-color: #2e6da4 !important; } .timeline-badge.success { background-color: #3f903f !important; } .timeline-badge.warning { background-color: #f0ad4e !important; } .timeline-badge.danger { background-color: #d9534f !important; } .timeline-badge.info { background-color: #5bc0de !important; } .timeline-title { margin-top: 0; color: inherit; } .timeline-body > p, .timeline-body > ul { margin-bottom: 0; } .timeline-body > p + p { margin-top: 5px; } @media(max-width:767px) { ul.timeline:before { left: 40px; } ul.timeline > li > .timeline-panel { width: calc(100% - 90px); width: -moz-calc(100% - 90px); width: -webkit-calc(100% - 90px); } ul.timeline > li > .timeline-badge { top: 16px; left: 15px; margin-left: 0; } ul.timeline > li > .timeline-panel { float: right; } ul.timeline > li > .timeline-panel:before { right: auto; left: -15px; border-right-width: 15px; border-left-width: 0; } ul.timeline > li > .timeline-panel:after { right: auto; left: -14px; border-right-width: 14px; border-left-width: 0; } } ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/authority.html ================================================
          {{app}}
          授权规则
          资源名 流控应用 授权类型 操作
          {{ruleEntity.rule.resource}} {{ruleEntity.rule.limitApp }} 白名单 黑名单
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html ================================================

          未连接

          连接中

          已连接

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html ================================================

          独立模式 (Alone)

          嵌入模式 (Embedded)

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html ================================================
          {{app}}
          集群限流 - 机器分配/管控

          {{loadError.message}}

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html ================================================
          集群限流 - Token Client 列表

          {{loadError.message}}

          Client ID Server IP Server 端口 连接状态 操作
          {{clientVO.id}} {{clientVO.state.clientConfig.serverHost}} {{clientVO.state.clientConfig.serverPort}} 未连接 连接中 已连接
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html ================================================
          {{app}}
          Token Client 列表
          集群限流 - Token Server 列表

          {{loadError.message}}

          Server ID Port 命名空间集合 运行模式 总连接数 QPS 总览 操作
          {{serverVO.id}} {{serverVO.id}}(自主指定) {{serverVO.port}} {{serverVO.state.namespaceSetStr}} 未知 未知 嵌入模式 独立模式 {{serverVO.connectedCount}} 未知 {{serverVO.state.requestLimitDataStr}} 未知
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html ================================================
          {{app}}
          集群限流 - Token Server 总览

          {{loadError.message}}

          Server ID Port 命名空间集合 总连接数 连接情况 QPS 总览
          {{serverVO.id}} {{serverVO.port}} {{serverVO.state.namespaceSetStr}} {{serverVO.connectedCount}}

          namespace: {{cg.namespace}}, 连接数: {{cg.connectedCount}}, clients: {{generateConnectionSet(cg.connectionSet)}}

          namespace: {{crl.namespace}}, 当前 QPS: {{crl.currentQps}}, 最大允许 QPS: {{crl.maxAllowedQps}}

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html ================================================
          {{app}}
          集群限流

          {{loadError.message}}

          Client

          Server

          未开启

           Client    Server

          该机器未引入 Sentinel 集群限流客户端或服务端的相关依赖,请引入相关依赖。

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dashboard/home.html ================================================

          欢迎使用 Sentinel 控制台

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dashboard/main.html ================================================
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/degrade.html ================================================
          {{app}}
          熔断规则
          资源名 熔断策略 阈值 熔断时长(s) 操作
          {{rule.resource}} 慢调用比例 异常比例 异常数 {{rule.count}} {{rule.timeWindow}}s
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/authority-rule-dialog.html ================================================
          {{authorityRuleDialog.title}}
           白名单    黑名单
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html ================================================
          修改 Token Client 配置

          {{ccDialogData.clientId}}

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html ================================================
          {{serverAssignDialogData.title}}

          {{serverAssignDialogData.serverData.currentServer}}

           应用内机器    外部指定机器

          若指定外部 server,请先在相应页面对外部 server 进行配置,然后在此页面指定。

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html ================================================
          连接详情

          {{connectionDetailDialogData.serverData.id}}

          命名空间 连接数 连接详情
          {{cg.namespace}} {{cg.connectedCount}} {{generateConnectionSet(cg.connectionSet)}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/confirm-dialog.html ================================================
          {{confirmDialog.title}}

          {{confirmDialog.attentionTitle}}:

          {{confirmDialog.attention}}

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/degrade-rule-dialog.html ================================================
          {{degradeRuleDialog.title}}
           慢调用比例    异常比例    异常数
          s
          ms
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html ================================================
          {{flowRuleDialog.title}}
           QPS    并发线程数
           单机均摊    总体阈值
           直接    关联    链路  
           快速失败    Warm Up    排队等待
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/api-dialog.html ================================================
          {{gatewayApiDialog.title}}
           精确    前缀    正则  
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html ================================================
          {{gatewayFlowRuleDialog.title}}
           Route ID    Route ID    API 分组    API 分组  
           Client IP    Remote Host    Header    URL 参数    Cookie  
           精确    子串    正则  
           QPS    线程数  
           快速失败    匀速排队  
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html ================================================
          {{paramFlowRuleDialog.title}}

          QPS 模式

           单机均摊    总体阈值
           若选择,则 Token Server 不可用时将退化到单机限流

          参数值 参数类型 限流阈值 操作

          {{paramItem.classType}}

          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html ================================================
          {{systemRuleDialog.title}}
           LOAD    RT    线程数    入口 QPS    CPU 使用率  
           LOAD    RT    线程数    入口 QPS    CPU 使用率  
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html ================================================
          {{app}}
          流控规则
          资源名 来源应用 流控模式 阈值类型 阈值 阈值模式 流控效果 操作
          {{rule.resource}} {{rule.limitApp }} 直接 关联 链路 {{rule.grade==0 ? '线程数' : 'QPS'}} {{rule.count}} {{generateThresholdTypeShow(rule)}} 快速失败 Warm Up 排队等待 预热排队
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html ================================================
          {{app}}
          回到单机页面
          流控规则
          资源名 来源应用 流控模式 阈值类型 阈值 阈值模式 流控效果 操作
          {{rule.resource}} {{rule.limitApp }} 直接 关联 链路 {{rule.grade == 0 ? '线程数' : 'QPS'}} {{rule.count}} {{generateThresholdTypeShow(rule)}} 快速失败 Warm Up 排队等待 预热排队
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/gateway/api.html ================================================
          {{app}}
          API 分组管理
          API 名称 匹配模式 匹配串 操作
          {{api.apiName}} 精确 前缀 正则 {{api.pattern}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/gateway/flow.html ================================================
          {{app}}
          网关流控规则
          API 名称 API 类型 阈值类型 单机阈值 操作
          {{rule.resource}} Route ID API 分组 QPS 线程数 {{rule.count}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/gateway/identity.html ================================================
          {{app}}
          请求链路
          API 名称 API 类型 通过 QPS 拒绝 QPS 线程数 平均 RT 分钟通过 分钟拒绝 操作
          {{resource.resource}} Route ID 自定义 API {{resource.passQps}} {{resource.blockQps}} {{resource.threadNum}} {{resource.averageRt}} {{resource.oneMinutePass}} {{resource.oneMinuteBlock}} {{resource.oneMinuteBlock}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/identity.html ================================================
          {{app}}
          簇点链路
          资源名 通过QPS 拒绝QPS 并发数 平均RT 分钟通过 分钟拒绝 操作
          {{resource.resource}} {{resource.passQps}} {{resource.blockQps}} {{resource.threadNum}} {{resource.averageRt}} {{resource.oneMinutePass}} {{resource.oneMinuteBlock}} {{resource.oneMinuteBlock}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/login.html ================================================
          Sentinel Logo
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/machine.html ================================================
          {{app}}
          机器列表 实例总数 {{machines.length}}, 健康 {{healthyCount}}, 失联 {{machines.length - healthyCount}}.
          机器名 IP 地址 端口号 Sentinel 客户端版本 健康状态 心跳时间 操作
          {{entry.hostname}} {{entry.ip}} {{entry.port}} {{entry.version}} 健康 失联 {{entry.lastHeartbeat | date: 'yyyy/MM/dd HH:mm:ss'}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/metric.html ================================================
          {{app}}
          {{metricTypeDesc}} 实时监控
           {{metric.resource}}
          时间 通过 QPS 拒绝QPS 响应时间(ms)
          {{tableObj.timestamp | date: 'HH:mm:ss'}} {{tableObj.passQps | number : 1}} {{tableObj.blockQps | number : 1}} {{tableObj.rt | number : 1}}
          -
        1. ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/pagination.tpl.html ================================================ ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html ================================================
          {{app}}
          热点参数限流规则

          {{loadError.message}}

          资源名 参数索引 流控模式 阈值 是否集群 例外项数目 操作
          {{ruleEntity.rule.resource}} {{ruleEntity.rule.paramIdx}} {{ruleEntity.rule.grade == 1 ? 'QPS' : '未知'}} {{ruleEntity.rule.count}} {{ruleEntity.rule.paramFlowItemList == undefined ? 0 : ruleEntity.rule.paramFlowItemList.length}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/app/views/system.html ================================================
          {{app}}
          系统规则
          阈值类型 单机阈值 操作
          系统 load(自适应) 平均 RT 并发数 入口 QPS CPU 使用率 {{rule.highestSystemLoad}} {{rule.avgRt}} {{rule.maxThread}} {{rule.qps}} {{rule.highestCpuUsage}}
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/dist/css/app.css ================================================ .chat,.timeline{list-style:none}#loading-bar,#loading-bar-spinner{pointer-events:none;-webkit-pointer-events:none;-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar-spinner.ng-enter,#loading-bar-spinner.ng-leave.ng-leave-active,#loading-bar.ng-enter,#loading-bar.ng-leave.ng-leave-active{opacity:0}#loading-bar-spinner.ng-enter.ng-enter-active,#loading-bar-spinner.ng-leave,#loading-bar.ng-enter.ng-enter-active,#loading-bar.ng-leave{opacity:1}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:10002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px;opacity:.45;-moz-box-shadow:#29d 1px 0 6px 1px;-ms-box-shadow:#29d 1px 0 6px 1px;-webkit-box-shadow:#29d 1px 0 6px 1px;box-shadow:#29d 1px 0 6px 1px;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}#loading-bar-spinner{display:block;position:fixed;z-index:10002;top:10px;left:10px}#loading-bar-spinner .spinner-icon{width:14px;height:14px;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:loading-bar-spinner .4s linear infinite;-moz-animation:loading-bar-spinner .4s linear infinite;-ms-animation:loading-bar-spinner .4s linear infinite;-o-animation:loading-bar-spinner .4s linear infinite;animation:loading-bar-spinner .4s linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:table-cell;vertical-align:middle;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch span::before{content:"\200b"}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;margin:0;z-index:-1;opacity:0;filter:alpha(opacity=0);visibility:hidden}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px}.ngdialog,.ngdialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0}@-webkit-keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{box-sizing:border-box;overflow:auto;-webkit-overflow-scrolling:touch;z-index:10000}.ngdialog *,.ngdialog :after,.ngdialog :before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation,.ngdialog.ngdialog-disabled-animation .ngdialog-content,.ngdialog.ngdialog-disabled-animation .ngdialog-overlay{-webkit-animation:none!important;animation:none!important}.ngdialog-overlay{background:rgba(0,0,0,.4);-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s}.ngdialog-no-overlay{pointer-events:none}.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-content{background:#fff;-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s;pointer-events:all}.ngdialog.ngdialog-closing .ngdialog-content{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-close:before{font-family:Helvetica,Arial,sans-serif;content:'\00D7';cursor:pointer}body.ngdialog-open,html.ngdialog-open{overflow:hidden}@-webkit-keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}.ngdialog.ngdialog-theme-default{padding-bottom:160px;padding-top:160px}.ngdialog.ngdialog-theme-default.ngdialog-closing .ngdialog-content{-webkit-animation:ngdialog-flyout .5s;animation:ngdialog-flyout .5s}.ngdialog.ngdialog-theme-default .ngdialog-content{-webkit-animation:ngdialog-flyin .5s;animation:ngdialog-flyin .5s;background:#f0f0f0;border-radius:5px;color:#444;font-family:Helvetica,sans-serif;font-size:1.1em;line-height:1.5em;margin:0 auto;max-width:100%;padding:1em;position:relative;width:450px}.ngdialog.ngdialog-theme-default .ngdialog-close{border-radius:5px;cursor:pointer;position:absolute;right:0;top:0}.ngdialog.ngdialog-theme-default .ngdialog-close:before{background:0 0;border-radius:3px;color:#bbb;content:'\00D7';font-size:26px;font-weight:400;height:30px;line-height:26px;position:absolute;right:3px;text-align:center;top:3px;width:30px}.ngdialog.ngdialog-theme-default .ngdialog-close:active:before,.ngdialog.ngdialog-theme-default .ngdialog-close:hover:before{color:#777}.ngdialog.ngdialog-theme-default .ngdialog-message{margin-bottom:.5em}.ngdialog.ngdialog-theme-default .ngdialog-input{margin-bottom:1em}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url],.ngdialog.ngdialog-theme-default .ngdialog-input textarea{background:#fff;border:0;border-radius:3px;font-family:inherit;font-size:inherit;font-weight:inherit;margin:0 0 .25em;min-height:2.5em;padding:.25em .67em;width:100%}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input textarea:focus{box-shadow:inset 0 0 0 2px #8dbdf1;outline:0}.ngdialog.ngdialog-theme-default .ngdialog-buttons:after{content:'';display:table;clear:both}.ngdialog.ngdialog-theme-default .ngdialog-button{border:0;border-radius:3px;cursor:pointer;float:right;font-family:inherit;font-size:.8em;letter-spacing:.1em;line-height:1em;margin:0 0 0 .5em;padding:.75em 2em;text-transform:uppercase}.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:ngdialog-pulse 1.1s infinite;animation:ngdialog-pulse 1.1s infinite;outline:0}.btn:active,.btn:focus,.selectize-input>input:focus{outline:0!important}@media (max-width:568px){.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:none;animation:none}}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-primary{background:#3288e6;color:#fff}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-secondary{background:#e0e0e0;color:#777}.datetimepicker{border-radius:4px;direction:ltr;display:block;margin-top:1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:320px}.datetimepicker>div{display:none}.datetimepicker .hour,.datetimepicker .minute{height:34px;line-height:34px;margin:0;width:25%}.datetimepicker .table{margin:0}.datetimepicker .table td,.datetimepicker .table th{border:0;border-radius:4px;height:20px;text-align:center}.datetimepicker .day:hover,.datetimepicker .hour:hover,.datetimepicker .left:hover,.datetimepicker .minute:hover,.datetimepicker .right:hover,.datetimepicker .switch:hover{background:#eee;cursor:pointer}.datetimepicker .disabled,.datetimepicker .disabled:hover{background:0 0;color:#ebebeb;cursor:default}.datetimepicker .active,.datetimepicker .active.disabled,.datetimepicker .active.disabled:hover,.datetimepicker .active:hover{background-color:#04c;background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;color:#fff;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#08c', endColorstr='#04c', GradientType=0);text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datetimepicker .current,.datetimepicker .current.disabled,.datetimepicker .current.disabled:hover,.datetimepicker .current:hover{background-color:#e5e5e5}.datetimepicker .active.active,.datetimepicker .active.disabled,.datetimepicker .active.disabled.active,.datetimepicker .active.disabled.disabled,.datetimepicker .active.disabled:active,.datetimepicker .active.disabled:hover,.datetimepicker .active.disabled:hover.active,.datetimepicker .active.disabled:hover.disabled,.datetimepicker .active.disabled:hover:active,.datetimepicker .active.disabled:hover:hover,.datetimepicker .active:active,.datetimepicker .active:hover,.datetimepicker .active:hover.active,.datetimepicker .active:hover.disabled,.datetimepicker .active:hover:active,.datetimepicker .active:hover:hover,.datetimepicker span.active.disabled:hover[disabled],.datetimepicker span.active.disabled[disabled],.datetimepicker span.active:hover[disabled],.datetimepicker span.active[disabled],.datetimepicker td.active.disabled:hover[disabled],.datetimepicker td.active.disabled[disabled],.datetimepicker td.active:hover[disabled],.datetimepicker td.active[disabled]{background-color:#04c}.datetimepicker span{border-radius:4px;cursor:pointer;display:block;float:left;height:54px;line-height:54px;margin:1%;width:23%}.datetimepicker span:hover{background:#eee}.datetimepicker .future,.datetimepicker .past{color:#999}.ui-notification{position:fixed;z-index:9999;width:300px;-webkit-transition:all ease .5s;-o-transition:all ease .5s;transition:all ease .5s;color:#fff;border-radius:0;background:#337ab7;box-shadow:5px 5px 10px rgba(0,0,0,.3)}.ui-notification.clickable{cursor:pointer}.ui-notification.clickable:hover{opacity:.7}.ui-notification.killed{-webkit-transition:opacity ease 1s;-o-transition:opacity ease 1s;transition:opacity ease 1s;opacity:0}.ui-notification>h3{font-size:14px;font-weight:700;display:block;margin:10px 10px 0;padding:0 0 5px;text-align:left;border-bottom:1px solid rgba(255,255,255,.3)}.ui-notification a{color:#fff}.ui-notification a:hover{text-decoration:underline}.ui-notification>.message{margin:10px}.ui-notification.warning{color:#fff;background:#f0ad4e}.ui-notification.error{color:#fff;background:#d9534f}.ui-notification.success{color:#fff;background:#5cb85c}.ui-notification.info{color:#fff;background:#5bc0de}table.rz-table{table-layout:fixed;border-collapse:collapse}table.rz-table th{position:relative;min-width:25px}table.rz-table th .rz-handle{width:10px;height:100%;position:absolute;top:0;right:0;cursor:ew-resize!important}table.rz-table th .rz-handle.rz-handle-active{border-right:1px dotted #000}.selectize-control.plugin-drag_drop.multi>.selectize-input>div.ui-sortable-placeholder{visibility:visible!important;background:#f2f2f2!important;background:rgba(0,0,0,.06)!important;border:0!important;-webkit-box-shadow:inset 0 0 12px 4px #fff;box-shadow:inset 0 0 12px 4px #fff}.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after{content:'!';visibility:hidden}.selectize-control.plugin-drag_drop .ui-sortable-helper{-webkit-box-shadow:0 2px 5px rgba(0,0,0,.2);box-shadow:0 2px 5px rgba(0,0,0,.2)}.selectize-dropdown-header{position:relative;padding:5px 8px;border-bottom:1px solid #d0d0d0;background:#f8f8f8;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.selectize-dropdown-header-close{position:absolute;right:8px;top:50%;color:#303030;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px!important}.selectize-dropdown-header-close:hover{color:#000}.selectize-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;float:left;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.selectize-control.plugin-remove_button [data-value] .remove,.selectize-input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;display:inline-block}.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.selectize-dropdown.plugin-optgroup_columns .optgroup:before{display:none}.selectize-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.selectize-control.plugin-remove_button [data-value]{position:relative;padding-right:24px!important}.selectize-control.plugin-remove_button [data-value] .remove{z-index:1;position:absolute;top:0;right:0;bottom:0;width:17px;text-align:center;font-weight:700;font-size:12px;color:inherit;text-decoration:none;vertical-align:middle;padding:2px 0 0;border-left:1px solid #d0d0d0;-webkit-border-radius:0 2px 2px 0;-moz-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0;box-sizing:border-box}.selectize-control.plugin-remove_button [data-value] .remove:hover{background:rgba(0,0,0,.05)}.selectize-control.plugin-remove_button [data-value].active .remove{border-left-color:#cacaca}.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover{background:0 0}.selectize-control.plugin-remove_button .disabled [data-value] .remove{border-left-color:#fff}.selectize-control.plugin-remove_button .remove-single{position:absolute;right:0;top:0;font-size:23px}.selectize-control,.selectize-input{position:relative}.selectize-dropdown,.selectize-input,.selectize-input input{color:#303030;font-family:inherit;font-size:13px;line-height:18px;-webkit-font-smoothing:inherit}.selectize-control.single .selectize-input.input-active,.selectize-input{background:#fff;cursor:text;display:inline-block}.selectize-input{border:1px solid #d0d0d0;padding:8px;width:100%;overflow:hidden;z-index:1;box-sizing:border-box;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.1);box-shadow:inset 0 1px 1px rgba(0,0,0,.1);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.selectize-control.multi .selectize-input.has-items{padding:6px 8px 3px}.selectize-input.full{background-color:#fff}.selectize-input.disabled,.selectize-input.disabled *{cursor:default!important}.selectize-input.focus{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.15)}.selectize-input.dropdown-active{-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.selectize-input>*{vertical-align:baseline;display:-moz-inline-stack;display:inline-block;zoom:1}.selectize-control.multi .selectize-input>div{cursor:pointer;margin:0 3px 3px 0;padding:2px 6px;background:#f2f2f2;color:#303030;border:0 solid #d0d0d0}.selectize-control.multi .selectize-input>div.active{background:#e8e8e8;color:#303030;border:0 solid #cacaca}.selectize-control.multi .selectize-input.disabled>div,.selectize-control.multi .selectize-input.disabled>div.active{color:#7d7d7d;background:#fff;border:0 solid #fff}.selectize-input>input{display:inline-block!important;padding:0!important;min-height:0!important;max-height:none!important;max-width:100%!important;margin:0 2px 0 0!important;text-indent:0!important;border:0!important;background:0 0!important;line-height:inherit!important;-webkit-user-select:auto!important;-webkit-box-shadow:none!important;box-shadow:none!important}.selectize-input>input::-ms-clear{display:none}.selectize-input::after{content:' ';display:block;clear:left}.selectize-input.dropdown-active::before{content:' ';display:block;position:absolute;background:#f0f0f0;height:1px;bottom:0;left:0;right:0}.selectize-dropdown{position:absolute;z-index:10;border:1px solid #d0d0d0;background:#fff;margin:-1px 0 0;border-top:0 none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.1);-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.selectize-dropdown [data-selectable]{cursor:pointer;overflow:hidden}.selectize-dropdown [data-selectable] .highlight{background:rgba(125,168,208,.2);-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px}.selectize-dropdown .optgroup-header,.selectize-dropdown .option{padding:5px 8px}.selectize-dropdown .option,.selectize-dropdown [data-disabled],.selectize-dropdown [data-disabled] [data-selectable].option{cursor:inherit;opacity:.5}.selectize-dropdown [data-selectable].option{opacity:1}.selectize-dropdown .optgroup:first-child .optgroup-header{border-top:0 none}.selectize-dropdown .optgroup-header{color:#303030;background:#fff;cursor:default}.selectize-dropdown .active{background-color:#f5fafd;color:#495c68}.selectize-dropdown .active.create{color:#495c68}.selectize-dropdown .create{color:rgba(48,48,48,.5)}.selectize-dropdown-content{overflow-y:auto;overflow-x:hidden;max-height:200px;-webkit-overflow-scrolling:touch}.selectize-control.single .selectize-input,.selectize-control.single .selectize-input input{cursor:pointer}.selectize-control.single .selectize-input.input-active,.selectize-control.single .selectize-input.input-active input{cursor:text}.selectize-control.single .selectize-input:after{content:' ';display:block;position:absolute;top:50%;right:15px;margin-top:-3px;width:0;height:0;border-style:solid;border-width:5px 5px 0;border-color:grey transparent transparent}.selectize-control.single .selectize-input.dropdown-active:after{margin-top:-4px;border-width:0 5px 5px;border-color:transparent transparent grey}.selectize-control.rtl.single .selectize-input:after{left:15px;right:auto}.selectize-control.rtl .selectize-input>input{margin:0 4px 0 -2px!important}.selectize-control .selectize-input.disabled{opacity:.5;background-color:#fafafa}/*! * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) * Code licensed under the Apache License v2.0. * For details, see http://www.apache.org/licenses/LICENSE-2.0. */body{background-color:#f8f8f8}.example{padding:.625rem 1.825rem .625rem 2.5rem;border:1px dashed #ccc;position:relative;margin:0 0 .625rem;background-color:#fff}dl dd,dl dt{line-height:1.25rem}dl dt{font-style:normal;font-weight:700}dl dd{margin-left:.9375rem}dl.horizontal dt{float:left;width:10rem;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}dl.horizontal dd{margin-left:11.25rem}#wrapper{width:100%}#page-wrapper{min-height:568px;background-color:#fff}@media(min-width:768px){#page-wrapper{position:inherit;margin:0 0 0 250px;padding:0 30px;border-left:1px solid #e7e7e7}}.navbar-top-links{margin-right:0}.navbar-top-links li{display:inline-block}.flot-chart,.navbar-top-links .dropdown-menu li{display:block}.navbar-top-links li:last-child{margin-right:15px}.navbar-top-links li a{padding:15px;min-height:50px}.navbar-top-links .dropdown-menu li:last-child{margin-right:0}.navbar-top-links .dropdown-menu li a{padding:3px 20px;min-height:0}.navbar-top-links .dropdown-menu li a div{white-space:normal}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{width:310px;min-width:0}.navbar-top-links .dropdown-messages{margin-left:5px}.navbar-top-links .dropdown-tasks{margin-left:-59px}.navbar-top-links .dropdown-alerts{margin-left:-123px}.navbar-top-links .dropdown-user{right:0;left:auto}.sidebar .sidebar-search{padding:15px}.sidebar ul li{border-bottom:1px solid #e7e7e7}.sidebar ul li a.active{background-color:#fff;color:#fff}.sidebar .arrow{float:right}.sidebar .fa.arrow:before{content:"\f104"}.sidebar .active>a>.fa.arrow:before{content:"\f107"}.sidebar .nav-second-level li,.sidebar .nav-third-level li{border-bottom:0!important}.sidebar .nav-second-level li a{padding-left:37px}.sidebar .nav-third-level li a{padding-left:52px}@media(min-width:768px){.sidebar{z-index:1;position:absolute;width:250px;margin-top:51px}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{margin-left:auto}}.btn-outline{color:inherit;background-color:transparent;transition:all .5s}.btn-primary.btn-outline{color:#428bca}.btn-success.btn-outline{color:#5cb85c}.btn-info.btn-outline{color:#5bc0de}.btn-warning.btn-outline{color:#f0ad4e}.btn-danger.btn-outline{color:#d9534f}.btn-danger.btn-outline:hover,.btn-info.btn-outline:hover,.btn-primary.btn-outline:hover,.btn-success.btn-outline:hover,.btn-warning.btn-outline:hover{color:#fff}.chat{margin:0;padding:0}.chat li{margin-bottom:10px;padding-bottom:5px;border-bottom:1px dotted #999}.chat li.left .chat-body{margin-left:60px}.chat li.right .chat-body{margin-right:60px}.chat li .chat-body p{margin:0}.chat .glyphicon,.panel .slidedown .glyphicon{margin-right:5px}.chat-panel .panel-body{height:350px;overflow-y:scroll}.login-panel{margin-top:25%}.flot-chart{height:400px}.flot-chart-content{width:100%;height:100%}.dataTables_wrapper{position:relative;clear:both}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{background:0 0}table.dataTable thead .sorting_asc:after{content:"\f0de";float:right;font-family:fontawesome}table.dataTable thead .sorting_desc:after{content:"\f0dd";float:right;font-family:fontawesome}table.dataTable thead .sorting:after{content:"\f0dc";float:right;font-family:fontawesome;color:rgba(50,50,50,.5)}.btn-circle{width:30px;height:30px;padding:6px 0;border-radius:15px;text-align:center;font-size:12px;line-height:1.428571429}.btn-circle.btn-lg{width:50px;height:50px;padding:10px 16px;border-radius:25px;font-size:18px;line-height:1.33}.btn-circle.btn-xl{width:70px;height:70px;padding:10px 16px;border-radius:35px;font-size:24px;line-height:1.33}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;border:1px solid #ddd;background-color:#eee!important}.show-grid{margin:15px 0}.huge{font-size:40px}.panel-green{border-color:#5cb85c}.panel-green .panel-heading{border-color:#5cb85c;color:#fff;background-color:#5cb85c}.panel-green a{color:#5cb85c}.panel-green a:hover{color:#3d8b3d}.panel-red{border-color:#d9534f}.panel-red .panel-heading{border-color:#d9534f;color:#fff;background-color:#d9534f}.panel-red a{color:#d9534f}.panel-red a:hover{color:#b52b27}.panel-yellow{border-color:#f0ad4e}.panel-yellow .panel-heading{border-color:#f0ad4e;color:#fff;background-color:#f0ad4e}.panel-yellow a{color:#f0ad4e}.panel-yellow a:hover{color:#df8a13}.timeline{position:relative;padding:20px 0}.timeline:before{content:" ";position:absolute;top:0;bottom:0;left:50%;width:3px;margin-left:-1.5px;background-color:#eee}.timeline>li{position:relative;margin-bottom:20px}.timeline>li:after,.timeline>li:before{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-panel{float:left;position:relative;width:46%;padding:20px;border:1px solid #d4d4d4;border-radius:2px;-webkit-box-shadow:0 1px 6px rgba(0,0,0,.175);box-shadow:0 1px 6px rgba(0,0,0,.175)}.timeline>li>.timeline-panel:before{content:" ";display:inline-block;position:absolute;top:26px;right:-15px;border-top:15px solid transparent;border-right:0 solid #ccc;border-bottom:15px solid transparent;border-left:15px solid #ccc}.timeline>li>.timeline-panel:after{content:" ";display:inline-block;position:absolute;top:27px;right:-14px;border-top:14px solid transparent;border-right:0 solid #fff;border-bottom:14px solid transparent;border-left:14px solid #fff}.timeline>li>.timeline-badge{z-index:100;position:absolute;top:16px;left:50%;width:50px;height:50px;margin-left:-25px;border-radius:50%;text-align:center;font-size:1.4em;line-height:50px;color:#fff;background-color:#999}.timeline>li.timeline-inverted>.timeline-panel{float:right}.timeline>li.timeline-inverted>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}.timeline>li.timeline-inverted>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}.timeline-badge.primary{background-color:#2e6da4!important}.timeline-badge.success{background-color:#3f903f!important}.timeline-badge.warning{background-color:#f0ad4e!important}.timeline-badge.danger{background-color:#d9534f!important}.timeline-badge.info{background-color:#5bc0de!important}.timeline-title{margin-top:0;color:inherit}.timeline-body>p,.timeline-body>ul{margin-bottom:0}.timeline-body>p+p{margin-top:5px}@media(max-width:767px){ul.timeline:before{left:40px}ul.timeline>li>.timeline-panel{width:calc(100% - 90px);width:-moz-calc(100% - 90px);width:-webkit-calc(100% - 90px);float:right}ul.timeline>li>.timeline-badge{top:16px;left:15px;margin-left:0}ul.timeline>li>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}ul.timeline>li>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}}.header,.jumbotron{border-bottom:1px solid #e5e5e5}.btn{height:32px}.width-200{max-width:200px}.width-300,.witdh-300{max-width:300px}body{padding:0}.footer,.header,.marketing{padding-left:15px;padding-right:15px}.header{margin-bottom:10px}.header h3{margin-top:0;margin-bottom:0;line-height:40px;padding-bottom:19px}.card .detail,.card .detail-brand{line-height:98px;text-align:center}.footer{padding-top:19px;color:#777;border-top:1px solid #e5e5e5}.container-narrow>hr{margin:30px 0}.jumbotron{text-align:center}.jumbotron .btn{font-size:21px;padding:14px 24px}.marketing{margin:40px 0}.marketing p+h4{margin-top:28px}@media screen and (min-width:768px){.container{width:inherit;margin-left:60px;margin-right:5px}.footer,.header,.marketing{padding-left:0;padding-right:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}.navbar-inverse .navbar-nav>li>a{color:#b0ddce;font-size:15px}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#1b926c}@media (min-width:900px){.navbar-right,.navbar-right~.navbar-right{margin-right:0}.navbar-left{float:left!important}.navbar-right{float:right!important}}.dropdown-menu{min-width:100px!important}.nav-sidebar li.active a{background:#DDD}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background:#1d9d74;color:#fff}.broadcast-message,.broadcast-message-preview{padding:10px;text-align:center;background:#555;color:#BBB;margin-top:50px}.card{position:relative;border:1px solid #d9d9d9;color:#666;background-color:#fff;width:100%;border-radius:5px}.card .card-header,.tools-header{border-top-left-radius:4px;border-top-right-radius:4px}.card .card-header{padding:9px 0;height:40px;background:#555;color:#fff;text-align:center}.card .card-body{padding:12px 10px}.card .card-footer{height:20px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .detail-brand{float:left;width:30%;font-size:30px;color:#fff}.card .default{background:#1d9d74}.card .info{background:#6EBEE7}.card .warn{background:#ED7F54}.card .danger{background:#6583BE}.card .detail .text-default{color:#1d9d74}.card .detail .text-info{color:#6EBEE7}.card .detail .text-warn{color:#ED7F54}.card .detail .text-danger{color:#6583BE}.card .detail{float:right;width:70%}.card .detail .text{font-size:12px}.card .detail .number{font-size:30px;font-weight:500}.h100{height:100px}.inline{display:inline}.separator{height:1px;background-color:#e5e5e5;margin-top:10px}.card>.card-body>table>tbody>tr>td,.card>.card-body>table>thead>tr>td{word-wrap:break-word;word-break:break-all}.card>.card-body>table>thead>tr>td{font-weight:500;font-size:13px;text-align:center}.card>.card-body>table>thead>tr>td>span{font-weight:500;font-size:10px}.card>.card-body>table>tbody>tr>td{font-size:12px;text-align:center}.card>.card-body>table>tbody>tr>td>a{color:#666}.thumbnails>.card>.card-body>table>tbody>tr>td,.thumbnails>.card>.card-body>table>thead>tr>td{font-size:12px;color:#777;word-wrap:break-word;word-break:break-all}.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(n+2){text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(n+2){font-weight:700;text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(1),.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(1){text-align:left}.tools-header{background:#f5f5f5;padding:9px 0;height:40px}.tools-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.tools-header .brand>a{color:#666}.tools-header>a,.tools-header>button,.tools-header>select{float:right;max-width:80px;margin:1px 10px;height:25px;padding:0 10px;line-height:25px;color:#666}.tools-header .paged{margin-right:0}.btn.btn-danger-tag{color:#fff;background-color:#d9534f;border-color:#d43f3a;line-height:1px;font-size:11px;padding:4px}.btn.btn-danger{color:#333;background-color:#fff;border-color:#ccc}.btn.btn-danger:active,.btn.btn-danger:focus,.btn.btn-danger:hover{color:#d9534f;border-color:#d9534f;background:#fff}.form-control{height:32px}.input-label:before{display:inline-block;content:"*";color:#f44336;font-family:SimSun;font-size:12px;-webkit-transform:TranslateX(-10px);-ms-transform:TranslateX(-10px);transform:TranslateX(-10px)}.badge-main,.label.label-main{color:#fff;background-color:#1d9d74;border-color:#1d9d74}.bootstrap-tagsinput{background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);display:inline-block;padding:4px 6px;color:#555;vertical-align:middle;border-radius:4px;width:85%;height:100px;line-height:20px;cursor:text}.bootstrap-tagsinput>.dropdown-menu{min-width:40px;font-size:12px}.bootstrap-tagsinput>.dropdown-menu>.active>a,.bootstrap-tagsinput>.dropdown-menu>.active>a:focus,.bootstrap-tagsinput>.dropdown-menu>.active>a:hover{background-image:-webkit-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-o-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#1d9d74),to(#1d9d74));background-image:linear-gradient(to bottom,#1d9d74 0,#1d9d74 100%);filter:progid: DXImageTransform.Microsoft.gradient(startColorstr='#1d9d74', endColorstr='#1d9d74', GradientType=0);background-repeat:repeat-x;color:#fff;text-decoration:none;outline:0;background-color:#1d9d74}.inputs-header{padding:9px 0;height:50px;border-top-left-radius:4px;border-top-right-radius:4px}.inputs-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.inputs-header .brand>a{color:#666}.inputs-header>input{float:right;margin:1px 10px;height:30px;padding:0 10px;color:#666}.inputs-header>a{float:right;margin:1px 10px;height:30px;padding:5 5px}.inputs-header>select{float:right;max-width:80px;margin:1px 10px;padding:0 10px;color:#666;height:25px;font-size:12px}.witdh-150{max-width:150px}.witdh-200{max-width:200px}.card.highlight{border-color:#d9534f}.card .pagination-footer{height:40px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .pagination-footer .tools{font-size:12px;margin:11px 20px 11px 0;float:right;display:inline}.card>.pagination-footer>.tools>span>input{height:25px;max-width:50px;display:inline}.pagination{display:inline-block;padding-left:0;margin:8px 0;float:right;border-radius:4px}.pagination>a{margin-right:5px;height:28px;width:28px;padding:5px 0}.datepicker>.table>tbody>tr>td,.datepicker>.table>thead>tr>td,.timepicker>.table>tbody>tr>td,.timepicker>.table>thead>tr>td{padding:5px 3px}.datepicker>.table>tbody>tr>td>.btn,.datepicker>.table>thead>tr>td>.btn,.timepicker>.table>tbody>tr>td>.btn,.timepicker>.table>thead>tr>td>.btn{border:1px solid #FFFDFD}.datepicker>.table>tbody>tr>td>.btn-default:active,.datepicker>.table>tbody>tr>td>.btn-default:focus,.datepicker>.table>tbody>tr>td>.btn-default:hover,.datepicker>.table>thead>tr>td>.btn-default:active,.datepicker>.table>thead>tr>td>.btn-default:focus,.datepicker>.table>thead>tr>td>.btn-default:hover,.timepicker>.table>tbody>tr>td>.btn-default:active,.timepicker>.table>tbody>tr>td>.btn-default:focus,.timepicker>.table>tbody>tr>td>.btn-default:hover,.timepicker>.table>thead>tr>td>.btn-default:active,.timepicker>.table>thead>tr>td>.btn-default:focus,.timepicker>.table>thead>tr>td>.btn-default:hover{color:#1d9d74;border-color:#1d9d74;background:#fff}.datepicker>.table>tbody>tr>td>a,.datepicker>.table>thead>tr>td>a,.timepicker>.table>tbody>tr>td>a,.timepicker>.table>thead>tr>td>a{height:25px;width:25px;padding:3px 0}.datepicker>.table>tbody>tr:first-child>td>a{padding:4px 0}.datepicker>.table>tbody>tr>td>a.btn.active,.datepicker>.table>thead>tr>td>a.btn.active,.timepicker>.table>tbody>tr>td>a.btn.active,.timepicker>.table>thead>tr>td>a.btn.active{color:#1d9d74;border-color:#1d9d74;background:#fff;box-shadow:inset 0 0 0 rgba(0,0,0,.125)}.datepicker>.table>thead>tr>td:not(:first-child):last-child>a,.timepicker>.table>thead>tr>td:not(:first-child):last-child>a{height:25px;width:50px;padding:5px 0}.datepicker>.table>tbody>tr>td>a,.timepicker>.table>tbody>tr>td>a{margin-left:8px}.sortorder:after{content:'\25b2'}.sortorder.reverse:after{content:'\25bc'}.input-control select{-moz-appearance:none;-webkit-appearance:none;appearance:none;position:relative;border:1px solid #d9d9d9;width:100%;height:100%;padding:.3125rem;z-index:0}.navbar-inverse{background-color:#337ab7;border-color:#337ab7}.sidebar{z-index:1;width:220px;top:0;left:0;height:100%}#page-wrapper{position:inherit;margin:70px 0 0 220px;padding:12px 30px;border-left:0 solid #e7e7e7}.sidebar .sidebar-nav.navbar-collapse{background-color:#F5F5F5;position:relative;color:#000;width:100%;padding:0;margin:0;list-style:none inside}.sidebar a{color:#555}.sidebar ul li:hover{color:red}.form-control{border-radius:8px}.form-control:focus,.highlight-border{border-color:#337ab7;box-shadow:0 0 0 rgba(0,0,0,.075) inset,0 0 0 rgba(29,157,116,1)}.btn-outline-primary.focus,.btn-outline-primary:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.browsehappy{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.btn.btn-main{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default-inverse,.btn-default-inverse:focus,.btn-default-inverse:hover,.btn-default:active{color:#337ab7;border-color:#337ab7;background:#fff}.btn-danger-inverse,.btn-danger-inverse:focus,.btn-danger-inverse:hover,.btn-danger:active{color:#d9534f;border-color:#d9534f;background:#fff}.btn-tab-active,.btn-tab-active:focus,.btn-tab-active:hover,.btn-tab-default:active,.btn-tab-default:focus,.btn-tab-default:hover{color:#337ab7;border-color:#337ab7;background:#fff;font-weight:600}.btn-tab-default{color:#777;background:#fff;font-weight:600}.pagination>.btn.active{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default:active,.btn-default:focus,.btn-default:hover{color:#337ab7;border-color:#337ab7;background:#fff}.bootstrap-switch.bootstrap-switch-on{border-color:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#337ab7}.selectize-input-200>.selectize-input{min-width:200px;border-color:#337ab7}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-secondary.focus,.btn-outline-secondary:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-success.focus,.btn-outline-success:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-info.focus,.btn-outline-info:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-warning.focus,.btn-outline-warning:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-danger.focus,.btn-outline-danger:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-light.focus,.btn-outline-light:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-dark.focus,.btn-outline-dark:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40} ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/dist/js/app.js ================================================ "use strict";var app;angular.module("sentinelDashboardApp",["oc.lazyLoad","ui.router","ui.bootstrap","angular-loading-bar","ngDialog","ui.bootstrap.datetimepicker","ui-notification","rzTable","angular-clipboard","selectize","angularUtils.directives.dirPagination"]).factory("AuthInterceptor",["$window","$state",function(r,t){return{responseError:function(e){return 401===e.status&&(r.localStorage.removeItem("session_sentinel_admin"),t.go("login")),e},response:function(e){return e},request:function(e){var t=r.document.getElementsByTagName("base")[0].href;return e.url=t+e.url,e},requestError:function(e){return e}}}]).config(["$stateProvider","$urlRouterProvider","$ocLazyLoadProvider","$httpProvider",function(e,t,r,a){a.interceptors.push("AuthInterceptor"),r.config({debug:!1,events:!0}),t.otherwise("/dashboard/home"),e.state("login",{url:"/login",templateUrl:"app/views/login.html",controller:"LoginCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/login.js"]})}]}}).state("dashboard",{url:"/dashboard",templateUrl:"app/views/dashboard/main.html",resolve:{loadMyDirectives:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/directives/header/header.js","app/scripts/directives/sidebar/sidebar.js","app/scripts/directives/sidebar/sidebar-search/sidebar-search.js"]})}]}}).state("dashboard.home",{url:"/home",templateUrl:"app/views/dashboard/home.html",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/main.js"]})}]}}).state("dashboard.flowV1",{templateUrl:"app/views/flow_v1.html",url:"/flow/:app",controller:"FlowControllerV1",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow_v1.js"]})}]}}).state("dashboard.flow",{templateUrl:"app/views/flow_v2.html",url:"/v2/flow/:app",controller:"FlowControllerV2",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow_v2.js"]})}]}}).state("dashboard.paramFlow",{templateUrl:"app/views/param_flow.html",url:"/paramFlow/:app",controller:"ParamFlowController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/param_flow.js"]})}]}}).state("dashboard.clusterAppAssignManage",{templateUrl:"app/views/cluster_app_assign_manage.html",url:"/cluster/assign_manage/:app",controller:"SentinelClusterAppAssignManageController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_assign_manage.js"]})}]}}).state("dashboard.clusterAppServerList",{templateUrl:"app/views/cluster_app_server_list.html",url:"/cluster/server/:app",controller:"SentinelClusterAppServerListController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_server_list.js"]})}]}}).state("dashboard.clusterAppClientList",{templateUrl:"app/views/cluster_app_client_list.html",url:"/cluster/client/:app",controller:"SentinelClusterAppTokenClientListController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_token_client_list.js"]})}]}}).state("dashboard.clusterSingle",{templateUrl:"app/views/cluster_single_config.html",url:"/cluster/single/:app",controller:"SentinelClusterSingleController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_single.js"]})}]}}).state("dashboard.authority",{templateUrl:"app/views/authority.html",url:"/authority/:app",controller:"AuthorityRuleController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/authority.js"]})}]}}).state("dashboard.degrade",{templateUrl:"app/views/degrade.html",url:"/degrade/:app",controller:"DegradeCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/degrade.js"]})}]}}).state("dashboard.system",{templateUrl:"app/views/system.html",url:"/system/:app",controller:"SystemCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/system.js"]})}]}}).state("dashboard.machine",{templateUrl:"app/views/machine.html",url:"/app/:app",controller:"MachineCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/machine.js"]})}]}}).state("dashboard.identity",{templateUrl:"app/views/identity.html",url:"/identity/:app",controller:"IdentityCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/identity.js"]})}]}}).state("dashboard.gatewayIdentity",{templateUrl:"app/views/gateway/identity.html",url:"/gateway/identity/:app",controller:"GatewayIdentityCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/gateway/identity.js"]})}]}}).state("dashboard.metric",{templateUrl:"app/views/metric.html",url:"/metric/:app",controller:"MetricCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/metric.js"]})}]}}).state("dashboard.gatewayApi",{templateUrl:"app/views/gateway/api.html",url:"/gateway/api/:app",controller:"GatewayApiCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/gateway/api.js"]})}]}}).state("dashboard.gatewayFlow",{templateUrl:"app/views/gateway/flow.html",url:"/gateway/flow/:app",controller:"GatewayFlowCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/gateway/flow.js"]})}]}})}]),(app=angular.module("sentinelDashboardApp")).filter("range",[function(){return function(e,t){if(isNaN(t)||t<=0)return[];e=[];for(var r=1;r<=t;r++)e.push(r);return e}}]),(app=angular.module("sentinelDashboardApp")).service("VersionService",["$http",function(e){this.version=function(){return e({url:"/version",method:"GET"})}}]),(app=angular.module("sentinelDashboardApp")).service("AuthService",["$http",function(t){this.check=function(){return t({url:"/auth/check",method:"POST"})},this.login=function(e){return t({url:"/auth/login",params:e,method:"POST"})},this.logout=function(){return t({url:"/auth/logout",method:"POST"})}}]),(app=angular.module("sentinelDashboardApp")).service("AppService",["$http",function(e){this.getApps=function(){return e({url:"app/briefinfos.json",method:"GET"})}}]),(app=angular.module("sentinelDashboardApp")).service("FlowServiceV1",["$http",function(a){function t(e){return void 0===e||""===e||isNaN(e)||e<=0}this.queryMachineRules=function(e,t,r){return a({url:"/v1/flow/rules",params:{app:e,ip:t,port:r},method:"GET"})},this.newRule=function(e){e.resource,e.limitApp,e.grade,e.count,e.strategy,e.refResource,e.controlBehavior,e.warmUpPeriodSec,e.maxQueueingTimeMs,e.app,e.ip,e.port;return a({url:"/v1/flow/rule",data:e,method:"POST"})},this.saveRule=function(e){var t={id:e.id,resource:e.resource,limitApp:e.limitApp,grade:e.grade,count:e.count,strategy:e.strategy,refResource:e.refResource,controlBehavior:e.controlBehavior,warmUpPeriodSec:e.warmUpPeriodSec,maxQueueingTimeMs:e.maxQueueingTimeMs};return a({url:"/v1/flow/save.json",params:t,method:"PUT"})},this.deleteRule=function(e){var t={id:e.id,app:e.app};return a({url:"/v1/flow/delete.json",params:t,method:"DELETE"})},this.checkRuleValid=function(e){return void 0===e.resource||""===e.resource?(alert("资源名称不能为空"),!1):void 0===e.count||e.count<0?(alert("限流阈值必须大于等于 0"),!1):void 0===e.strategy||e.strategy<0?(alert("无效的流控模式"),!1):1!=e.strategy&&2!=e.strategy||void 0!==e.refResource&&""!=e.refResource?void 0===e.controlBehavior||e.controlBehavior<0?(alert("无效的流控整形方式"),!1):1==e.controlBehavior&&t(e.warmUpPeriodSec)?(alert("预热时长必须大于 0"),!1):2==e.controlBehavior&&t(e.maxQueueingTimeMs)?(alert("排队超时时间必须大于 0"),!1):!e.clusterMode||void 0!==e.clusterConfig&&void 0!==e.clusterConfig.thresholdType||(alert("集群限流配置不正确"),!1):(alert("请填写关联资源或入口"),!1)}}]),(app=angular.module("sentinelDashboardApp")).service("FlowServiceV2",["$http",function(a){function t(e){return void 0===e||""===e||isNaN(e)||e<=0}this.queryMachineRules=function(e,t,r){return a({url:"/v2/flow/rules",params:{app:e,ip:t,port:r},method:"GET"})},this.newRule=function(e){return a({url:"/v2/flow/rule",data:e,method:"POST"})},this.saveRule=function(e){return a({url:"/v2/flow/rule/"+e.id,data:e,method:"PUT"})},this.deleteRule=function(e){return a({url:"/v2/flow/rule/"+e.id,method:"DELETE"})},this.checkRuleValid=function(e){return void 0===e.resource||""===e.resource?(alert("资源名称不能为空"),!1):void 0===e.count||e.count<0?(alert("限流阈值必须大于等于 0"),!1):void 0===e.strategy||e.strategy<0?(alert("无效的流控模式"),!1):1!=e.strategy&&2!=e.strategy||void 0!==e.refResource&&""!=e.refResource?void 0===e.controlBehavior||e.controlBehavior<0?(alert("无效的流控整形方式"),!1):1==e.controlBehavior&&t(e.warmUpPeriodSec)?(alert("预热时长必须大于 0"),!1):2==e.controlBehavior&&t(e.maxQueueingTimeMs)?(alert("排队超时时间必须大于 0"),!1):!e.clusterMode||void 0!==e.clusterConfig&&void 0!==e.clusterConfig.thresholdType||(alert("集群限流配置不正确"),!1):(alert("请填写关联资源或入口"),!1)}}]),(app=angular.module("sentinelDashboardApp")).service("DegradeService",["$http",function(a){this.queryMachineRules=function(e,t,r){return a({url:"degrade/rules.json",params:{app:e,ip:t,port:r},method:"GET"})},this.newRule=function(e){return a({url:"/degrade/rule",data:e,method:"POST"})},this.saveRule=function(e){var t={id:e.id,resource:e.resource,limitApp:e.limitApp,grade:e.grade,count:e.count,timeWindow:e.timeWindow,statIntervalMs:e.statIntervalMs,minRequestAmount:e.minRequestAmount,slowRatioThreshold:e.slowRatioThreshold};return a({url:"/degrade/rule/"+e.id,data:t,method:"PUT"})},this.deleteRule=function(e){return a({url:"/degrade/rule/"+e.id,method:"DELETE"})},this.checkRuleValid=function(e){if(void 0===e.resource||""===e.resource)return alert("资源名称不能为空"),!1;if(void 0===e.grade||e.grade<0)return alert("未知的降级策略"),!1;if(void 0===e.count||""===e.count||e.count<0)return alert("降级阈值不能为空或小于 0"),!1;if(null==e.timeWindow||""===e.timeWindow||e.timeWindow<=0)return alert("熔断时长必须大于 0s"),!1;if(null==e.minRequestAmount||e.minRequestAmount<=0)return alert("最小请求数目需大于 0"),!1;if(null==e.statIntervalMs||e.statIntervalMs<=0)return alert("统计窗口时长需大于 0s"),!1;if(void 0!==e.statIntervalMs&&12e4=r?n.apply(null,t):function(){return e(t.concat([].slice.apply(arguments)))}}(e)}function n(){var n=arguments,r=n.length-1;return function(){for(var e=r,t=n[r].apply(this,arguments);e--;)t=n[e].call(this,t);return t}}function l(){for(var e=[],t=0;tthis._limit&&this.evict(),e},e.prototype.evict=function(){var t=this._items.shift();return this._evictListeners.forEach(function(e){return e(t)}),t},e.prototype.dequeue=function(){if(this.size())return this._items.splice(0,1)[0]},e.prototype.clear=function(){var e=this._items;return this._items=[],e},e.prototype.size=function(){return this._items.length},e.prototype.remove=function(e){var t=this._items.indexOf(e);return-1 "+Ue(e))},e.prototype.traceTransitionIgnored=function(e){this.enabled(g.Category.TRANSITION)&&console.log(st(e)+": Ignored <> "+Ue(e))},e.prototype.traceHookInvocation=function(e,t,n){if(this.enabled(g.Category.HOOK)){var r=S("traceData.hookType")(n)||"internal",i=S("traceData.context.state.name")(n)||S("traceData.context")(n)||"unknown",o=He(e.registeredHook.callback);console.log(st(t)+": Hook -> "+r+" context: "+i+", "+Fe(200,o))}},e.prototype.traceHookResult=function(e,t,n){this.enabled(g.Category.HOOK)&&console.log(st(t)+": <- Hook returned: "+Fe(200,Ue(e)))},e.prototype.traceResolvePath=function(e,t,n){this.enabled(g.Category.RESOLVE)&&console.log(st(n)+": Resolving "+e+" ("+t+")")},e.prototype.traceResolvableResolved=function(e,t){this.enabled(g.Category.RESOLVE)&&console.log(st(t)+": <- Resolved "+e+" to: "+Fe(200,Ue(e.data)))},e.prototype.traceError=function(e,t){this.enabled(g.Category.TRANSITION)&&console.log(st(t)+": <- Rejected "+Ue(t)+", reason: "+e)},e.prototype.traceSuccess=function(e,t){this.enabled(g.Category.TRANSITION)&&console.log(st(t)+": <- Success "+Ue(t)+", final state: "+e.name)},e.prototype.traceUIViewEvent=function(e,t,n){void 0===n&&(n=""),this.enabled(g.Category.UIVIEW)&&console.log("ui-view: "+Le(30,e)+" "+et(t)+n)},e.prototype.traceUIViewConfigUpdated=function(e,t){this.enabled(g.Category.UIVIEW)&&this.traceUIViewEvent("Updating",e," with ViewConfig from context='"+t+"'")},e.prototype.traceUIViewFill=function(e,t){this.enabled(g.Category.UIVIEW)&&this.traceUIViewEvent("Fill",e," with: "+Fe(200,t))},e.prototype.traceViewSync=function(e){if(this.enabled(g.Category.VIEWCONFIG)){var a="uiview component fqn",t=e.map(function(e){var t,n=e.uiView,r=e.viewConfig,i=n&&n.fqn,o=r&&r.viewDecl.$context.name+": ("+r.viewDecl.$name+")";return(t={})[a]=i,t["view config state (view name)"]=o,t}).sort(function(e,t){return(e[a]||"").localeCompare(t[a]||"")});it(t)}},e.prototype.traceViewServiceEvent=function(e,t){var n,r,i;this.enabled(g.Category.VIEWCONFIG)&&console.log("VIEWCONFIG: "+e+" "+(r=(n=t).viewDecl,i=r.$context.name||"(root)","[View#"+n.$id+" from '"+i+"' state]: target ui-view: '"+r.$uiViewName+"@"+r.$uiViewContextAnchor+"'"))},e.prototype.traceViewServiceUIViewEvent=function(e,t){this.enabled(g.Category.VIEWCONFIG)&&console.log("VIEWCONFIG: "+e+" "+et(t))},e}(),ut=new lt,ct=function(){function e(e){this.pattern=/.*/,this.inherit=!0,N(this,e)}return e.prototype.is=function(e,t){return!0},e.prototype.encode=function(e,t){return e},e.prototype.decode=function(e,t){return e},e.prototype.equals=function(e,t){return e==t},e.prototype.$subPattern=function(){var e=this.pattern.toString();return e.substr(1,e.length-2)},e.prototype.toString=function(){return"{ParamType:"+this.name+"}"},e.prototype.$normalize=function(e){return this.is(e)?e:this.decode(e)},e.prototype.$asArray=function(e,t){if(!e)return this;if("auto"===e&&!t)throw new Error("'auto' array mode is for query parameters only");return new dt(this,e)},e}();function dt(r,i){var o=this;function a(e){return E(e)?e:k(e)?[e]:[]}function s(n,r){return function(e){if(E(e)&&0===e.length)return e;var t=ce(a(e),n);return!0===r?0===se(t,function(e){return!e}).length:function(e){switch(e.length){case 0:return;case 1:return"auto"===i?e[0]:e;default:return e}}(t)}}function l(o){return function(e,t){var n=a(e),r=a(t);if(n.length!==r.length)return!1;for(var i=0;i=n.invokeLimit&&n.deregister()}}},o.prototype.handleHookResult=function(e){var t=this,n=this.getNotCurrentRejection();return n||(R(e)?e.then(function(e){return t.handleHookResult(e)}):(ut.traceHookResult(e,this.transition,this.options),!1===e?Ve.aborted("Hook aborted transition").toPromise():h($t)(e)?Ve.redirected(e).toPromise():void 0))},o.prototype.getNotCurrentRejection=function(){var e=this.transition.router;return e._disposed?Ve.aborted("UIRouter instance #"+e.$id+" has been stopped (disposed)").toPromise():this.transition._aborted?Ve.aborted().toPromise():this.isSuperseded()?Ve.superseded(this.options.current()).toPromise():void 0},o.prototype.toString=function(){var e=this.options,t=this.registeredHook;return(S("traceData.hookType")(e)||"internal")+" context: "+(S("traceData.context.state.name")(e)||S("traceData.context")(e)||"unknown")+", "+Fe(200,Ye(t.callback))},o.HANDLE_RESULT=function(t){return function(e){return t.handleHookResult(e)}},o.LOG_REJECTED_RESULT=function(t){return function(e){R(e)&&e.catch(function(e){return t.logError(Ve.normalize(e))})}},o.LOG_ERROR=function(t){return function(e){return t.logError(e)}},o.REJECT_ERROR=function(e){return function(e){return Pe(e)}},o.THROW_ERROR=function(e){return function(e){throw e}},o}();function Gt(e,t){var i=O(t)?[t]:t;return!!(D(i)?i:function(e){for(var t=i,n=0;n "+(this.valid()?"":"(X) ")+"'"+(T(t)?t.name:t)+"'"+Ue(n(this.params()))+" )"},t.diToken=t}();function en(e,t){var n=["",""],r=e.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!t)return r;switch(t.squash){case!1:n=["(",")"+(t.isOptional?"?":"")];break;case!0:r=r.replace(/\/$/,""),n=["(?:/(",")|/)?"];break;default:n=["("+t.squash+"|",")?"]}return r+n[0]+t.type.pattern.source+n[1]}var tn=Xe("/"),nn={state:{params:{}},strict:!0,caseInsensitive:!0},rn=function(){function m(o,a,e,t){var s=this;this._cache={path:[this]},this._children=[],this._params=[],this._segments=[],this._compiled=[],this.config=t=te(t,nn),this.pattern=o;for(var n,r,i,l=/([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,u=/([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,c=[],d=0,p=function(e){if(!m.nameValidator.test(e))throw new Error("Invalid parameter name '"+e+"' in pattern '"+o+"'");if(le(s._params,v("id",e)))throw new Error("Duplicate parameter name '"+e+"' in pattern '"+o+"'")},h=function(e,t){var n,r=e[2]||e[3],i=t?e[4]:e[4]||("*"===e[1]?"[\\s\\S]*":null);return{id:r,regexp:i,segment:o.substring(d,e.index),type:i?a.type(i)||(n=i,W(a.type(t?"query":"path"),{pattern:new RegExp(n,s.config.caseInsensitive?"i":void 0)})):null}};(n=l.exec(o))&&!(0<=(r=h(n,!1)).segment.indexOf("?"));)p(r.id),this._params.push(e.fromPath(r.id,r.type,t.state)),this._segments.push(r.segment),c.push([r.segment,De(this._params)]),d=l.lastIndex;var f=(i=o.substring(d)).indexOf("?");if(0<=f){var g=i.substring(f);if(i=i.substring(0,f),0r.weight?s:r}return r},t.prototype.sync=function(e){if(!e||!e.defaultPrevented){var t=this._router,n=t.urlService,r=t.stateService,i={path:n.path(),search:n.search(),hash:n.hash()},o=this.match(i);m([[O,function(e){return n.url(e,!0)}],[$t.isDef,function(e){return r.go(e.state,e.params,e.options)}],[h($t),function(e){return r.go(e.state(),e.params(),e.options())}]])(o&&o.rule.handler(o.match,i,t))}},t.prototype.listen=function(e){var t=this;if(!1!==e)return this._stopFn=this._stopFn||this._router.urlService.onChange(function(e){return t.sync(e)});this._stopFn&&this._stopFn(),delete this._stopFn},t.prototype.update=function(e){var t=this._router.locationService;e?this.location=t.url():t.url()!==this.location&&t.url(this.location,!0)},t.prototype.push=function(e,t,n){var r=n&&!!n.replace;this._router.urlService.url(e.format(t||{}),r)},t.prototype.href=function(e,t,n){var r=e.format(t);if(null==r)return null;n=n||{absolute:!1};var i,o,a,s,l=this._router.urlService.config,u=l.html5Mode();if(u||null===r||(r="#"+l.hashPrefix()+r),i=r,o=u,a=n.absolute,r="/"===(s=l.baseHref())?i:o?We(s)+i:a?s.slice(1)+i:i,!n.absolute||!r)return r;var c=!u&&r?"/":"",d=l.port(),p=80===d||443===d?"":":"+d;return[l.protocol(),"://",l.host(),p,c,r].join("")},t.prototype.rule=function(e){var t=this;if(!ln.isUrlRule(e))throw new Error("invalid rule");return e.$id=this._id++,e.priority=e.priority||0,this._rules.push(e),this._sorted=!1,function(){return t.removeRule(e)}},t.prototype.removeRule=function(e){Q(this._rules,e)},t.prototype.rules=function(){return this.ensureSorted(),this._rules.slice()},t.prototype.otherwise=function(e){var t=pn(e);this._otherwiseFn=this.urlRuleFactory.create(f(!0),t),this._sorted=!1},t.prototype.initial=function(e){var t=pn(e);this.rule(this.urlRuleFactory.create(function(e,t){return 0===t.globals.transitionHistory.size()&&!!/^\/?$/.exec(e.path)},t))},t.prototype.when=function(e,t,n){var r=this.urlRuleFactory.create(e,t);return k(n&&n.priority)&&(r.priority=n.priority),this.rule(r),r},t.prototype.deferIntercept=function(e){void 0===e&&(e=!0),this.interceptDeferred=e},t}();function pn(e){if(!(D(e)||O(e)||h($t)(e)||$t.isDef(e)))throw new Error("'handler' must be a string, function, TargetState, or have a state: 'newtarget' property");return D(e)?e:f(e)}var hn=function(){function l(e){var n=this;this.router=e,this._uiViews=[],this._viewConfigs=[],this._viewConfigFactories={},this._listeners=[],this._pluginapi={_rootViewContext:this._rootViewContext.bind(this),_viewConfigFactory:this._viewConfigFactory.bind(this),_registeredUIView:function(t){return le(n._uiViews,function(e){return n.router.$id+"."+e.id===t})},_registeredUIViews:function(){return n._uiViews},_activeViewConfigs:function(){return n._viewConfigs},_onSync:function(e){return n._listeners.push(e),function(){return Q(n._listeners,e)}}}}return l.normalizeUIViewTarget=function(e,t){void 0===t&&(t="");var n=t.split("@"),r=n[0]||"$default",i=O(n[1])?n[1]:"^",o=/^(\^(?:\.\^)*)\.(.*$)/.exec(r);o&&(i=o[1],r=o[2]),"!"===r.charAt(0)&&(r=r.substr(1),i="");/^(\^(?:\.\^)*)$/.exec(i)?i=i.split(".").reduce(function(e,t){return e.parent},e).name:"."===i&&(i=e.name);return{uiViewName:r,uiViewContextAnchor:i}},l.prototype._rootViewContext=function(e){return this._rootContext=e||this._rootContext},l.prototype._viewConfigFactory=function(e,t){this._viewConfigFactories[e]=t},l.prototype.createViewConfig=function(e,t){var n=this._viewConfigFactories[t.$type];if(!n)throw new Error("ViewService: No view config factory registered for type "+t.$type);var r=n(e,t);return E(r)?r:[r]},l.prototype.deactivateViewConfig=function(e){ut.traceViewServiceEvent("<- Removing",e),Q(this._viewConfigs,e)},l.prototype.activateViewConfig=function(e){ut.traceViewServiceEvent("-> Registering",e),this._viewConfigs.push(e)},l.prototype.sync=function(){var n=this,r=this._uiViews.map(function(e){return[e.fqn,e]}).reduce(ke,{});function i(e){for(var t=e.viewDecl.$context,n=0;++n&&t.parent;)t=t.parent;return n}var o=u(function(e,t,n,r){return t*(e(n)-e(r))}),e=this._uiViews.sort(o(function(e){var t=function(e){return e&&e.parent?t(e.parent)+1:1};return 1e4*e.fqn.split(".").length+t(e.creationContext)},1)).map(function(e){var t=n._viewConfigs.filter(l.matches(r,e));return 1 Registering",t);var e=this._uiViews;return e.filter(function(e){return e.fqn===t.fqn&&e.$type===t.$type}).length&&ut.traceViewServiceUIViewEvent("!!!! duplicate uiView named:",t),e.push(t),this.sync(),function(){-1!==e.indexOf(t)?(ut.traceViewServiceUIViewEvent("<- Deregistering",t),Q(e)(t)):ut.traceViewServiceUIViewEvent("Tried removing non-registered uiView",t)}},l.prototype.available=function(){return this._uiViews.map(w("fqn"))},l.prototype.active=function(){return this._uiViews.filter(w("$config")).map(w("name"))},l.matches=function(s,l){return function(e){if(l.$type!==e.viewDecl.$type)return!1;var t=e.viewDecl,n=t.$uiViewName.split("."),r=l.fqn.split(".");if(!q(n,r.slice(0-n.length)))return!1;var i=1-n.length||void 0,o=r.slice(0,i).join("."),a=s[o].creationContext;return t.$uiViewContextAnchor===(a&&a.name)}},l}(),fn=function(){function e(){this.params=new wt,this.lastStartedTransitionId=-1,this.transitionHistory=new Re([],1),this.successfulTransitions=new Re([],1)}return e.prototype.dispose=function(){this.transitionHistory.clear(),this.successfulTransitions.clear(),this.transition=null},e}(),gn=function(e){return e.reduce(function(e,t){return e[t]=I(t),e},{dispose:z})},mn=["url","path","search","hash","onChange"],vn=["port","protocol","host","baseHref","html5Mode","hashPrefix"],yn=["type","caseInsensitive","strictMode","defaultSquashPolicy"],wn=["sort","when","initial","otherwise","rules","rule","removeRule"],bn=["deferIntercept","listen","sync","match"],$n=function(){function e(e,t){void 0===t&&(t=!0),this.router=e,this.rules={},this.config={};var n=function(){return e.locationService};B(n,this,n,mn,t);var r=function(){return e.locationConfig};B(r,this.config,r,vn,t);var i=function(){return e.urlMatcherFactory};B(i,this.config,i,yn);var o=function(){return e.urlRouter};B(o,this.rules,o,wn),B(o,this,o,bn)}return e.prototype.url=function(e,t,n){},e.prototype.path=function(){},e.prototype.search=function(){},e.prototype.hash=function(){},e.prototype.onChange=function(e){},e.prototype.parts=function(){return{path:this.path(),search:this.search(),hash:this.hash()}},e.prototype.dispose=function(){},e.prototype.sync=function(e){},e.prototype.listen=function(e){},e.prototype.deferIntercept=function(e){},e.prototype.match=function(e){},e.locationServiceStub=gn(mn),e.locationConfigStub=gn(vn),e}(),_n=0,Cn=function(){function e(e,t){void 0===e&&(e=$n.locationServiceStub),void 0===t&&(t=$n.locationConfigStub),this.locationService=e,this.locationConfig=t,this.$id=_n++,this._disposed=!1,this._disposables=[],this.trace=ut,this.viewService=new hn(this),this.globals=new fn,this.transitionService=new zn(this),this.urlMatcherFactory=new sn,this.urlRouter=new dn(this),this.stateRegistry=new zt(this),this.stateService=new Bn(this),this.urlService=new $n(this),this._plugins={},this.viewService._pluginapi._rootViewContext(this.stateRegistry.root()),this.globals.$current=this.stateRegistry.root(),this.globals.current=this.globals.$current.self,this.disposable(this.globals),this.disposable(this.stateService),this.disposable(this.stateRegistry),this.disposable(this.transitionService),this.disposable(this.urlRouter),this.disposable(e),this.disposable(t)}return e.prototype.disposable=function(e){this._disposables.push(e)},e.prototype.dispose=function(e){var t=this;e&&D(e.dispose)?e.dispose(this):(this._disposed=!0,this._disposables.slice().forEach(function(e){try{"function"==typeof e.dispose&&e.dispose(t),Q(t._disposables,e)}catch(e){}}))},e.prototype.plugin=function(e,t){void 0===t&&(t={});var n=new e(this,t);if(!n.name)throw new Error("Required property `name` missing on plugin: "+n);return this._disposables.push(n),this._plugins[n.name]=n},e.prototype.getPlugin=function(e){return e?this._plugins[e]:de(this._plugins)},e}();function Sn(t){t.addResolvable(kt.fromData(Cn,t.router),""),t.addResolvable(kt.fromData(Jt,t),""),t.addResolvable(kt.fromData("$transition$",t),""),t.addResolvable(kt.fromData("$stateParams",t.params()),""),t.entering().forEach(function(e){t.addResolvable(kt.fromData("$state$",e),e)})}var kn=G(["$transition$",Jt]),Dn=function(e){var t=de(e.treeChanges()).reduce(fe,[]).reduce(ve,[]),n=function(e){return kn(e.token)?kt.fromData(e.token,null):e};t.forEach(function(e){e.resolvables=e.resolvables.map(n)})},xn=function(t){var e=t.to().redirectTo;if(e){var n=t.router.stateService;return D(e)?V.$q.when(e(t)).then(r):r(e)}function r(e){if(e)return e instanceof $t?e:O(e)?n.target(e,t.params(),t.options()):e.state||e.params?n.target(e.state||t.to(),e.params||t.params(),t.options()):void 0}};function On(n){return function(e,t){return(0,t.$$state()[n])(e,t)}}var Tn=On("onExit"),En=On("onRetain"),An=On("onEnter"),Pn=function(e){return new Et(e.treeChanges().to).resolvePath("EAGER",e).then(z)},Mn=function(e,t){return new Et(e.treeChanges().to).subContext(t.$$state()).resolvePath("LAZY",e).then(z)},Rn=function(e){return new Et(e.treeChanges().to).resolvePath("LAZY",e).then(z)},In=function(e){var t=V.$q,n=e.views("entering");if(n.length)return t.all(n.map(function(e){return t.when(e.load())})).then(z)},Vn=function(e){var t=e.views("entering"),n=e.views("exiting");if(t.length||n.length){var r=e.router.viewService;n.forEach(function(e){return r.deactivateViewConfig(e)}),t.forEach(function(e){return r.activateViewConfig(e)}),r.sync()}},Fn=function(e){var t=e.router.globals,n=function(){t.transition===e&&(t.transition=null)};e.onSuccess({},function(){t.successfulTransitions.enqueue(e),t.$current=e.$to(),t.current=t.$current.self,xe(e.params(),t.params)},{priority:1e4}),e.promise.then(n,n)},Ln=function(e){var t=e.options(),n=e.router.stateService,r=e.router.urlRouter;if("url"!==t.source&&t.location&&n.$current.navigable){var i={replace:"replace"===t.location};r.push(n.$current.navigable.url,n.params,i)}r.update(!0)},jn=function(a){var s=a.router;var e=a.entering().filter(function(e){return!!e.$$state().lazyLoad}).map(function(e){return Hn(a,e)});return V.$q.all(e).then(function(){if("url"!==a.originalTransition().options().source){var e=a.targetState();return s.stateService.target(e.identifier(),e.params(),e.options())}var t=s.urlService,n=t.match(t.parts()),r=n&&n.rule;if(r&&"STATE"===r.type){var i=r.state,o=n.match;return s.stateService.target(i,o,a.options())}s.urlService.sync()})};function Hn(t,n){var r=n.$$state().lazyLoad,e=r._promise;if(!e){e=r._promise=V.$q.when(r(t,n)).then(function(e){e&&Array.isArray(e.states)&&e.states.forEach(function(e){return t.router.stateRegistry.register(e)});return e}).then(function(e){return delete n.lazyLoad,delete n.$$state().lazyLoad,delete r._promise,e},function(e){return delete r._promise,V.$q.reject(e)})}return e}var Yn=function(e,t,n,r,i,o,a,s){void 0===i&&(i=!1),void 0===o&&(o=Wt.HANDLE_RESULT),void 0===a&&(a=Wt.REJECT_ERROR),void 0===s&&(s=!1),this.name=e,this.hookPhase=t,this.hookOrder=n,this.criteriaMatchPath=r,this.reverseSort=i,this.getResultHandler=o,this.getErrorHandler=a,this.synchronous=s};function Nn(e){var t=e._ignoredReason();if(t){ut.traceTransitionIgnored(e);var n=e.router.globals.transition;return"SameAsCurrent"===t&&n&&n.abort(),Ve.ignored().toPromise()}}function qn(e){if(!e.valid())throw new Error(e.error().toString())}var Un={location:!0,relative:null,inherit:!1,notify:!0,reload:!1,custom:{},current:function(){return null},source:"unknown"},zn=function(){function e(e){this._transitionCount=0,this._eventTypes=[],this._registeredHooks={},this._criteriaPaths={},this._router=e,this.$view=e.viewService,this._deregisterHookFns={},this._pluginapi=B(f(this),{},f(this),["_definePathType","_defineEvent","_getPathTypes","_getEvents","getHooks"]),this._defineCorePaths(),this._defineCoreEvents(),this._registerCoreTransitionHooks(),e.globals.successfulTransitions.onEvict(Dn)}return e.prototype.onCreate=function(e,t,n){},e.prototype.onBefore=function(e,t,n){},e.prototype.onStart=function(e,t,n){},e.prototype.onExit=function(e,t,n){},e.prototype.onRetain=function(e,t,n){},e.prototype.onEnter=function(e,t,n){},e.prototype.onFinish=function(e,t,n){},e.prototype.onSuccess=function(e,t,n){},e.prototype.onError=function(e,t,n){},e.prototype.dispose=function(e){de(this._registeredHooks).forEach(function(t){return t.forEach(function(e){e._deregistered=!0,Q(t,e)})})},e.prototype.create=function(e,t){return new Jt(e,t,this._router)},e.prototype._defineCoreEvents=function(){var e=g.TransitionHookPhase,t=Wt,n=this._criteriaPaths;this._defineEvent("onCreate",e.CREATE,0,n.to,!1,t.LOG_REJECTED_RESULT,t.THROW_ERROR,!0),this._defineEvent("onBefore",e.BEFORE,0,n.to),this._defineEvent("onStart",e.RUN,0,n.to),this._defineEvent("onExit",e.RUN,100,n.exiting,!0),this._defineEvent("onRetain",e.RUN,200,n.retained),this._defineEvent("onEnter",e.RUN,300,n.entering),this._defineEvent("onFinish",e.RUN,400,n.to),this._defineEvent("onSuccess",e.SUCCESS,0,n.to,!1,t.LOG_REJECTED_RESULT,t.LOG_ERROR,!0),this._defineEvent("onError",e.ERROR,0,n.to,!1,t.LOG_REJECTED_RESULT,t.LOG_ERROR,!0)},e.prototype._defineCorePaths=function(){var e=g.TransitionHookScope.STATE,t=g.TransitionHookScope.TRANSITION;this._definePathType("to",t),this._definePathType("from",t),this._definePathType("exiting",e),this._definePathType("retained",e),this._definePathType("entering",e)},e.prototype._defineEvent=function(e,t,n,r,i,o,a,s){void 0===i&&(i=!1),void 0===o&&(o=Wt.HANDLE_RESULT),void 0===a&&(a=Wt.REJECT_ERROR),void 0===s&&(s=!1);var l=new Yn(e,t,n,r,i,o,a,s);this._eventTypes.push(l),Qt(this,this,l)},e.prototype._getEvents=function(t){return(k(t)?this._eventTypes.filter(function(e){return e.hookPhase===t}):this._eventTypes.slice()).sort(function(e,t){var n=e.hookPhase-t.hookPhase;return 0===n?e.hookOrder-t.hookOrder:n})},e.prototype._definePathType=function(e,t){this._criteriaPaths[e]={name:e,scope:t}},e.prototype._getPathTypes=function(){return this._criteriaPaths},e.prototype.getHooks=function(e){return this._registeredHooks[e]},e.prototype._registerCoreTransitionHooks=function(){var e=this._deregisterHookFns;e.addCoreResolves=this.onCreate({},Sn),e.ignored=this.onBefore({},Nn,{priority:-9999}),e.invalid=this.onBefore({},qn,{priority:-1e4}),e.redirectTo=this.onStart({to:function(e){return!!e.redirectTo}},xn),e.onExit=this.onExit({exiting:function(e){return!!e.onExit}},Tn),e.onRetain=this.onRetain({retained:function(e){return!!e.onRetain}},En),e.onEnter=this.onEnter({entering:function(e){return!!e.onEnter}},An),e.eagerResolve=this.onStart({},Pn,{priority:1e3}),e.lazyResolve=this.onEnter({entering:f(!0)},Mn,{priority:1e3}),e.resolveAll=this.onFinish({},Rn,{priority:1e3}),e.loadViews=this.onFinish({},In),e.activateViews=this.onSuccess({},Vn),e.updateGlobals=this.onCreate({},Fn),e.updateUrl=this.onSuccess({},Ln,{priority:9999}),e.lazyLoad=this.onBefore({entering:function(e){return!!e.lazyLoad}},jn)},e}(),Bn=function(){function n(e){this.router=e,this.invalidCallbacks=[],this._defaultErrorHandler=function(e){e instanceof Error&&e.stack?(console.error(e),console.error(e.stack)):e instanceof Ve?(console.error(e.toString()),e.detail&&e.detail.stack&&console.error(e.detail.stack)):console.error(e)};var t=Object.keys(n.prototype).filter(d(G(["current","$current","params","transition"])));B(f(n.prototype),this,f(this),t)}return Object.defineProperty(n.prototype,"transition",{get:function(){return this.router.globals.transition},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"params",{get:function(){return this.router.globals.params},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"current",{get:function(){return this.router.globals.current},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"$current",{get:function(){return this.router.globals.$current},enumerable:!0,configurable:!0}),n.prototype.dispose=function(){this.defaultErrorHandler(z),this.invalidCallbacks=[]},n.prototype._handleInvalidTargetState=function(e,n){var r=this,i=_t.makeTargetState(this.router.stateRegistry,e),t=this.router.globals,o=function(){return t.transitionHistory.peekTail()},a=o(),s=new Re(this.invalidCallbacks.slice()),l=new Et(e).injector(),u=function(e){if(e instanceof $t){var t=e;return(t=r.target(t.identifier(),t.params(),t.options())).valid()?o()!==a?Ve.superseded().toPromise():r.transitionTo(t.identifier(),t.params(),t.options()):Ve.invalid(t.error()).toPromise()}};return function t(){var e=s.dequeue();return void 0===e?Ve.invalid(n.error()).toPromise():V.$q.when(e(n,i,l)).then(u).then(function(e){return e||t()})}()},n.prototype.onInvalid=function(e){return this.invalidCallbacks.push(e),function(){Q(this.invalidCallbacks)(e)}.bind(this)},n.prototype.reload=function(e){return this.transitionTo(this.current,this.params,{reload:!k(e)||e,inherit:!1,notify:!1})},n.prototype.go=function(e,t,n){var r=te(n,{relative:this.$current,inherit:!0},Un);return this.transitionTo(e,t,r)},n.prototype.target=function(e,t,n){if(void 0===n&&(n={}),T(n.reload)&&!n.reload.name)throw new Error("Invalid reload state object");var r=this.router.stateRegistry;if(n.reloadState=!0===n.reload?r.root():r.matcher.find(n.reload,n.relative),n.reload&&!n.reloadState)throw new Error("No such reload state '"+(O(n.reload)?n.reload:n.reload.name)+"'");return new $t(this.router.stateRegistry,e,t,n)},n.prototype.getCurrentPath=function(){var e=this,t=this.router.globals.successfulTransitions.peekTail();return t?t.treeChanges().to:[new bt(e.router.stateRegistry.root())]},n.prototype.transitionTo=function(e,t,n){var o=this;void 0===t&&(t={}),void 0===n&&(n={});var a=this.router,s=a.globals;n=te(n,Un);n=N(n,{current:function(){return s.transition}});var r=this.target(e,t,n),i=this.getCurrentPath();if(!r.exists())return this._handleInvalidTargetState(i,r);if(!r.valid())return Pe(r.error());var l=function(i){return function(e){if(e instanceof Ve){var t=a.globals.lastStartedTransitionId===i.$id;if(e.type===g.RejectType.IGNORED)return t&&a.urlRouter.update(),V.$q.when(s.current);var n=e.detail;if(e.type===g.RejectType.SUPERSEDED&&e.redirected&&n instanceof $t){var r=i.redirect(n);return r.run().catch(l(r))}if(e.type===g.RejectType.ABORTED)return t&&a.urlRouter.update(),V.$q.reject(e)}return o.defaultErrorHandler()(e),V.$q.reject(e)}},u=this.router.transitionService.create(i,r),c=u.run().catch(l(u));return Ae(c),N(c,{transition:u})},n.prototype.is=function(e,t,n){n=te(n,{relative:this.$current});var r=this.router.stateRegistry.matcher.find(e,n.relative);if(k(r)){if(this.$current!==r)return!1;if(!t)return!0;var i=r.parameters({inherit:!0,matchingKeys:t});return vt.equals(i,vt.values(i,t),this.params)}},n.prototype.includes=function(e,t,n){n=te(n,{relative:this.$current});var r=O(e)&&Me.fromString(e);if(r){if(!r.matches(this.$current.name))return!1;e=this.$current.name}var i=this.router.stateRegistry.matcher.find(e,n.relative),o=this.$current.includes;if(k(i)){if(!k(o[i.name]))return!1;if(!t)return!0;var a=i.parameters({inherit:!0,matchingKeys:t});return vt.equals(a,vt.values(a,t),this.params)}},n.prototype.href=function(e,t,n){n=te(n,{lossy:!0,inherit:!0,absolute:!1,relative:this.$current}),t=t||{};var r=this.router.stateRegistry.matcher.find(e,n.relative);if(!k(r))return null;n.inherit&&(t=this.params.$inherit(t,this.$current,r));var i=r&&n.lossy?r.navigable:r;return i&&void 0!==i.url&&null!==i.url?this.router.urlRouter.href(i.url,t,{absolute:n.absolute}):null},n.prototype.defaultErrorHandler=function(e){return this._defaultErrorHandler=e||this._defaultErrorHandler},n.prototype.get=function(e,t){var n=this.router.stateRegistry;return 0===arguments.length?n.get():n.get(e,t||this.$current)},n.prototype.lazyLoad=function(e,t){var n=this.get(e);if(!n||!n.lazyLoad)throw new Error("Can not lazy load "+e);var r=this.getCurrentPath(),i=_t.makeTargetState(this.router.stateRegistry,r);return Hn(t=t||this.router.transitionService.create(r,i),n)},n}(),Wn={when:function(n){return new Promise(function(e,t){return e(n)})},reject:function(n){return new Promise(function(e,t){t(n)})},defer:function(){var n={};return n.promise=new Promise(function(e,t){n.resolve=e,n.reject=t}),n},all:function(e){if(E(e))return Promise.all(e);if(T(e)){var t=Object.keys(e).map(function(t){return e[t].then(function(e){return{key:t,val:e}})});return Wn.all(t).then(function(e){return e.reduce(function(e,t){return e[t.key]=t.val,e},{})})}}},Gn={},Kn=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,Qn=/([^\s,]+)/g,Zn={get:function(e){return Gn[e]},has:function(e){return null!=Zn.get(e)},invoke:function(e,t,n){var r=N({},Gn,n||{}),i=Zn.annotate(e),o=be(function(e){return r.hasOwnProperty(e)},function(e){return"DI can't find injectable: '"+e+"'"}),a=i.filter(o).map(function(e){return r[e]});return D(e)?e.apply(t,a):e.slice(-1)[0].apply(t,a)},annotate:function(e){if(!M(e))throw new Error("Not an injectable function: "+e);if(e&&e.$inject)return e.$inject;if(E(e))return e.slice(0,-1);var t=e.toString().replace(Kn,"");return t.slice(t.indexOf("(")+1,t.indexOf(")")).match(Qn)||[]}},Xn=function(e,t){var n=t[0],r=t[1];return e.hasOwnProperty(n)?E(e[n])?e[n].push(r):e[n]=[e[n],r]:e[n]=r,e},Jn=function(e){return e.split("&").filter(U).map(Qe).reduce(Xn,{})};function er(e){var t=function(e){return e||""},n=Ge(e).map(t),r=n[0],i=n[1],o=Ke(r).map(t);return{path:o[0],search:o[1],hash:i,url:e}}var tr=function(e){var t=e.path(),n=e.search(),r=e.hash(),i=Object.keys(n).map(function(t){var e=n[t];return(E(e)?e:[e]).map(function(e){return t+"="+e})}).reduce(fe,[]).join("&");return t+(i?"?"+i:"")+(r?"#"+r:"")};function nr(r,i,o,a){return function(e){var t=e.locationService=new o(e),n=e.locationConfig=new a(e,i);return{name:r,service:t,configuration:n,dispose:function(e){e.dispose(t),e.dispose(n)}}}}var rr,ir,or,ar=function(){function e(e,t){var n=this;this.fireAfterUpdate=t,this._listeners=[],this._listener=function(t){return n._listeners.forEach(function(e){return e(t)})},this.hash=function(){return er(n._get()).hash},this.path=function(){return er(n._get()).path},this.search=function(){return Jn(er(n._get()).search)},this._location=F.location,this._history=F.history}return e.prototype.url=function(t,e){return void 0===e&&(e=!0),k(t)&&t!==this._get()&&(this._set(null,null,t,e),this.fireAfterUpdate&&this._listeners.forEach(function(e){return e({url:t})})),tr(this)},e.prototype.onChange=function(e){var t=this;return this._listeners.push(e),function(){return Q(t._listeners,e)}},e.prototype.dispose=function(e){ee(this._listeners)},e}(),sr=(rr=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}rr(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),lr=function(n){function e(e){var t=n.call(this,e,!1)||this;return F.addEventListener("hashchange",t._listener,!1),t}return sr(e,n),e.prototype._get=function(){return Ze(this._location.hash)},e.prototype._set=function(e,t,n,r){this._location.hash=n},e.prototype.dispose=function(e){n.prototype.dispose.call(this,e),F.removeEventListener("hashchange",this._listener)},e}(ar),ur=(ir=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}ir(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),cr=function(t){function e(e){return t.call(this,e,!0)||this}return ur(e,t),e.prototype._get=function(){return this._url},e.prototype._set=function(e,t,n,r){this._url=n},e}(ar),dr=(or=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}or(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),pr=function(n){function e(e){var t=n.call(this,e,!0)||this;return t._config=e.urlService.config,F.addEventListener("popstate",t._listener,!1),t}return dr(e,n),e.prototype._getBasePrefix=function(){return We(this._config.baseHref())},e.prototype._get=function(){var e=this._location,t=e.pathname,n=e.hash,r=e.search;r=Ke(r)[1],n=Ge(n)[1];var i=this._getBasePrefix(),o=t===this._config.baseHref(),a=t.substr(0,i.length)===i;return(t=o?"/":a?t.substring(i.length):t)+(r?"?"+r:"")+(n?"#"+n:"")},e.prototype._set=function(e,t,n,r){var i=this._getBasePrefix(),o=n&&"/"!==n[0]?"/":"",a=""===n||"/"===n?this._config.baseHref():i+o+n;r?this._history.replaceState(e,t,a):this._history.pushState(e,t,a)},e.prototype.dispose=function(e){n.prototype.dispose.call(this,e),F.removeEventListener("popstate",this._listener)},e}(ar),hr=function(){var t=this;this.dispose=z,this._baseHref="",this._port=80,this._protocol="http",this._host="localhost",this._hashPrefix="",this.port=function(){return t._port},this.protocol=function(){return t._protocol},this.host=function(){return t._host},this.baseHref=function(){return t._baseHref},this.html5Mode=function(){return!1},this.hashPrefix=function(e){return k(e)?t._hashPrefix=e:t._hashPrefix}},fr=function(){function e(e,t){void 0===t&&(t=!1),this._isHtml5=t,this._baseHref=void 0,this._hashPrefix=""}return e.prototype.port=function(){return location.port?Number(location.port):"https"===this.protocol()?443:80},e.prototype.protocol=function(){return location.protocol.replace(/:/g,"")},e.prototype.host=function(){return location.hostname},e.prototype.html5Mode=function(){return this._isHtml5},e.prototype.hashPrefix=function(e){return k(e)?this._hashPrefix=e:this._hashPrefix},e.prototype.baseHref=function(e){return k(e)&&(this._baseHref=e),b(this._baseHref)&&(this._baseHref=this.getBaseHref()),this._baseHref},e.prototype.getBaseHref=function(){var e=document.getElementsByTagName("base")[0];return e&&e.href?e.href.replace(/^(https?:)?\/\/[^/]*/,""):location.pathname||"/"},e.prototype.dispose=function(){},e}();function gr(e){return V.$injector=Zn,{name:"vanilla.services",$q:V.$q=Wn,$injector:Zn,dispose:function(){return null}}}var mr=nr("vanilla.hashBangLocation",!1,lr,fr),vr=nr("vanilla.pushStateLocation",!0,pr,fr),yr=nr("vanilla.memoryLocation",!1,cr,hr),wr=function(){function e(){}return e.prototype.dispose=function(e){},e}(),br=Object.freeze({root:F,fromJson:j,toJson:H,forEach:Y,extend:N,equals:q,identity:U,noop:z,createProxyFunctions:B,inherit:W,inArray:G,_inArray:K,removeFrom:Q,_removeFrom:Z,pushTo:X,_pushTo:J,deregAll:ee,defaults:te,mergeR:ne,ancestors:re,pick:ie,omit:oe,pluck:ae,filter:se,find:le,mapObj:ue,map:ce,values:de,allTrueR:pe,anyTrueR:he,unnestR:fe,flattenR:ge,pushR:me,uniqR:ve,unnest:ye,flatten:we,assertPredicate:be,assertMap:$e,assertFn:_e,pairs:Ce,arrayTuples:Se,applyPairs:ke,tail:De,copy:xe,_extend:Oe,silenceUncaughtInPromise:Ae,silentRejection:Pe,notImplemented:I,services:V,Glob:Me,curry:u,compose:n,pipe:l,prop:w,propEq:v,parse:S,not:d,and:r,or:i,all:c,any:p,is:h,eq:o,val:f,invoke:a,pattern:m,isUndefined:b,isDefined:k,isNull:$,isNullOrUndefined:_,isFunction:D,isNumber:x,isString:O,isObject:T,isArray:E,isDate:A,isRegExp:P,isInjectable:M,isPromise:R,Queue:Re,maxLength:Fe,padString:Le,kebobString:je,functionToString:He,fnToString:Ye,stringify:Ue,beforeAfterSubstr:ze,hostRegex:Be,stripLastPathElement:We,splitHash:Ge,splitQuery:Ke,splitEqual:Qe,trimHashVal:Ze,splitOnDelim:Xe,joinNeighborsR:Je,get Category(){return g.Category},Trace:lt,trace:ut,get DefType(){return g.DefType},Param:vt,ParamTypes:yt,StateParams:wt,ParamType:ct,PathNode:bt,PathUtils:_t,resolvePolicies:Ct,defaultResolvePolicy:St,Resolvable:kt,NATIVE_INJECTOR_TOKEN:Tt,ResolveContext:Et,resolvablesBuilder:Lt,StateBuilder:Yt,StateObject:Nt,StateMatcher:qt,StateQueueManager:Ut,StateRegistry:zt,StateService:Bn,TargetState:$t,get TransitionHookPhase(){return g.TransitionHookPhase},get TransitionHookScope(){return g.TransitionHookScope},HookBuilder:Zt,matchState:Gt,RegisteredHook:Kt,makeEvent:Qt,get RejectType(){return g.RejectType},Rejection:Ve,Transition:Jt,TransitionHook:Wt,TransitionEventType:Yn,defaultTransOpts:Un,TransitionService:zn,UrlMatcher:rn,ParamFactory:an,UrlMatcherFactory:sn,UrlRouter:dn,UrlRuleFactory:ln,BaseUrlRule:un,UrlService:$n,ViewService:hn,UIRouterGlobals:fn,UIRouter:Cn,$q:Wn,$injector:Zn,BaseLocationServices:ar,HashLocationService:lr,MemoryLocationService:cr,PushStateLocationService:pr,MemoryLocationConfig:hr,BrowserLocationConfig:fr,keyValsToObjectR:Xn,getParams:Jn,parseUrl:er,buildUrl:tr,locationPluginFactory:nr,servicesPlugin:gr,hashLocationPlugin:mr,pushStateLocationPlugin:vr,memoryLocationPlugin:yr,UIRouterPluginBase:wr});function $r(){var n=null;return function(e,t){return n=n||V.$injector.get("$templateFactory"),[new kr(e,t,n)]}}var _r=function(e,n){return e.reduce(function(e,t){return e||k(n[t])},!1)};function Cr(r){if(!r.parent)return{};var i=["component","bindings","componentProvider"],o=["templateProvider","templateUrl","template","notify","async"].concat(["controller","controllerProvider","controllerAs","resolveAs"]),e=i.concat(o);if(k(r.views)&&_r(e,r))throw new Error("State '"+r.name+"' has a 'views' object. It cannot also have \"view properties\" at the state level. Move the following properties into a view (in the 'views' object): "+e.filter(function(e){return k(r[e])}).join(", "));var a={},t=r.views||{$default:ie(r,e)};return Y(t,function(e,t){if(t=t||"$default",O(e)&&(e={component:e}),e=N({},e),_r(i,e)&&_r(o,e))throw new Error("Cannot combine: "+i.join("|")+" with: "+o.join("|")+" in stateview: '"+t+"@"+r.name+"'");e.resolveAs=e.resolveAs||"$resolve",e.$type="ng1",e.$context=r,e.$name=t;var n=hn.normalizeUIViewTarget(e.$context,e.$name);e.$uiViewName=n.uiViewName,e.$uiViewContextAnchor=n.uiViewContextAnchor,a[t]=e}),a}var Sr=0,kr=function(){function e(e,t,n){var r=this;this.path=e,this.viewDecl=t,this.factory=n,this.$id=Sr++,this.loaded=!1,this.getTemplate=function(e,t){return r.component?r.factory.makeComponentTemplate(e,t,r.component,r.viewDecl.bindings):r.template}}return e.prototype.load=function(){var t=this,e=V.$q,n=new Et(this.path),r=this.path.reduce(function(e,t){return N(e,t.paramValues)},{}),i={template:e.when(this.factory.fromConfig(this.viewDecl,r,n)),controller:e.when(this.getController(n))};return e.all(i).then(function(e){return ut.traceViewServiceEvent("Loaded",t),t.controller=e.controller,N(t,e.template),t})},e.prototype.getController=function(e){var t=this.viewDecl.controllerProvider;if(!M(t))return this.viewDecl.controller;var n=V.$injector.annotate(t),r=E(t)?De(t):t;return new kt("",r,n).get(e)},e}(),Dr=function(){function e(){var r=this;this._useHttp=C.version.minor<3,this.$get=["$http","$templateCache","$injector",function(e,t,n){return r.$templateRequest=n.has&&n.has("$templateRequest")&&n.get("$templateRequest"),r.$http=e,r.$templateCache=t,r}]}return e.prototype.useHttpService=function(e){this._useHttp=e},e.prototype.fromConfig=function(e,t,n){var r=function(e){return V.$q.when(e).then(function(e){return{template:e}})},i=function(e){return V.$q.when(e).then(function(e){return{component:e}})};return k(e.template)?r(this.fromString(e.template,t)):k(e.templateUrl)?r(this.fromUrl(e.templateUrl,t)):k(e.templateProvider)?r(this.fromProvider(e.templateProvider,t,n)):k(e.component)?i(e.component):k(e.componentProvider)?i(this.fromComponentProvider(e.componentProvider,t,n)):r("")},e.prototype.fromString=function(e,t){return D(e)?e(t):e},e.prototype.fromUrl=function(e,t){return D(e)&&(e=e(t)),null==e?null:this._useHttp?this.$http.get(e,{cache:this.$templateCache,headers:{Accept:"text/html"}}).then(function(e){return e.data}):this.$templateRequest(e)},e.prototype.fromProvider=function(e,t,n){var r=V.$injector.annotate(e),i=E(e)?De(e):e;return new kt("",i,r).get(n)},e.prototype.fromComponentProvider=function(e,t,n){var r=V.$injector.annotate(e),i=E(e)?De(e):e;return new kt("",i,r).get(n)},e.prototype.makeComponentTemplate=function(l,u,e,c){c=c||{};var d=3<=C.version.minor?"::":"",p=function(e){var t=je(e);return/^(x|data)-/.exec(t)?"x-"+t:t},t=function(e){var t=V.$injector.get(e+"Directive");if(!t||!t.length)throw new Error("Unable to find component named '"+e+"'");return t.map(xr).reduce(fe,[])}(e).map(function(e){var t=e.name,n=e.type,r=p(t);if(l.attr(r)&&!c[t])return r+"='"+l.attr(r)+"'";var i=c[t]||t;if("@"===n)return r+"='{{"+d+"$resolve."+i+"}}'";if("&"!==n)return r+"='"+d+"$resolve."+i+"'";var o=u.getResolvable(i),a=o&&o.data,s=a&&V.$injector.annotate(a)||[];return r+"='$resolve."+i+(E(a)?"["+(a.length-1)+"]":"")+"("+s.join(",")+")'"}).join(" "),n=p(e);return"<"+n+" "+t+">"},e}();var xr=function(e){return T(e.bindToController)?Or(e.bindToController):Or(e.scope)},Or=function(t){return Object.keys(t||{}).map(function(e){return[e,/^([=<@&])[?]?(.*)/.exec(t[e])]}).filter(function(e){return k(e)&&E(e[1])}).map(function(e){return{name:e[1][2]||e[0],type:e[1][1]}})},Tr=function(){function n(e,t){this.stateRegistry=e,this.stateService=t,B(f(n.prototype),this,f(this))}return n.prototype.decorator=function(e,t){return this.stateRegistry.decorator(e,t)||this},n.prototype.state=function(e,t){return T(e)?t=e:t.name=e,this.stateRegistry.register(t),this},n.prototype.onInvalid=function(e){return this.stateService.onInvalid(e)},n}(),Er=function(n){return function(e,t){var i=e[n],o="onExit"===n?"from":"to";return i?function(e,t){var n=new Et(e.treeChanges(o)).subContext(t.$$state()),r=N(Wr(n),{$state$:t,$transition$:e});return V.$injector.invoke(i,this,r)}:void 0}},Ar=function(){function e(e){this._urlListeners=[],this.$locationProvider=e;var t=f(e);B(t,this,t,["hashPrefix"])}return e.monkeyPatchPathParameterType=function(e){var t=e.urlMatcherFactory.type("path");t.encode=function(e){return null!=e?e.toString().replace(/(~|\/)/g,function(e){return{"~":"~~","/":"~2F"}[e]}):e},t.decode=function(e){return null!=e?e.toString().replace(/(~~|~2F)/g,function(e){return{"~~":"~","~2F":"/"}[e]}):e}},e.prototype.dispose=function(){},e.prototype.onChange=function(e){var t=this;return this._urlListeners.push(e),function(){return Q(t._urlListeners)(e)}},e.prototype.html5Mode=function(){var e=this.$locationProvider.html5Mode();return(e=T(e)?e.enabled:e)&&this.$sniffer.history},e.prototype.baseHref=function(){return this._baseHref||(this._baseHref=this.$browser.baseHref()||this.$window.location.pathname)},e.prototype.url=function(e,t,n){return void 0===t&&(t=!1),k(e)&&this.$location.url(e),t&&this.$location.replace(),n&&this.$location.state(n),this.$location.url()},e.prototype._runtimeServices=function(e,t,n,r,i){var o=this;this.$location=t,this.$sniffer=n,this.$browser=r,this.$window=i,e.$on("$locationChangeSuccess",function(t){return o._urlListeners.forEach(function(e){return e(t)})});var a=f(t);B(a,this,a,["replace","path","search","hash"]),B(a,this,a,["port","protocol","host"])},e}(),Pr=function(){function n(e){this._router=e,this._urlRouter=e.urlRouter}return n.injectableHandler=function(t,n){return function(e){return V.$injector.invoke(n,null,{$match:e,$stateParams:t.globals.params})}},n.prototype.$get=function(){var e=this._urlRouter;return e.update(!0),e.interceptDeferred||e.listen(),e},n.prototype.rule=function(e){var t=this;if(!D(e))throw new Error("'rule' must be a function");var n=new un(function(){return e(V.$injector,t._router.locationService)},U);return this._urlRouter.rule(n),this},n.prototype.otherwise=function(e){var t=this,n=this._urlRouter;if(O(e))n.otherwise(e);else{if(!D(e))throw new Error("'rule' must be a string or function");n.otherwise(function(){return e(V.$injector,t._router.locationService)})}return this},n.prototype.when=function(e,t){return(E(t)||D(t))&&(t=n.injectableHandler(this._router,t)),this._urlRouter.when(e,t),this},n.prototype.deferIntercept=function(e){this._urlRouter.deferIntercept(e)},n}();C.module("ui.router.angular1",[]);var Mr=C.module("ui.router.init",["ng"]),Rr=C.module("ui.router.util",["ui.router.init"]),Ir=C.module("ui.router.router",["ui.router.util"]),Vr=C.module("ui.router.state",["ui.router.router","ui.router.util","ui.router.angular1"]),Fr=C.module("ui.router",["ui.router.init","ui.router.state","ui.router.angular1"]),Lr=(C.module("ui.router.compat",["ui.router"]),null);function jr(e){(Lr=this.router=new Cn).stateProvider=new Tr(Lr.stateRegistry,Lr.stateService),Lr.stateRegistry.decorator("views",Cr),Lr.stateRegistry.decorator("onExit",Er("onExit")),Lr.stateRegistry.decorator("onRetain",Er("onRetain")),Lr.stateRegistry.decorator("onEnter",Er("onEnter")),Lr.viewService._pluginapi._viewConfigFactory("ng1",$r());var s=Lr.locationService=Lr.locationConfig=new Ar(e);function t(e,t,n,r,i,o,a){return s._runtimeServices(i,e,r,t,n),delete Lr.router,delete Lr.$get,Lr}return Ar.monkeyPatchPathParameterType(Lr),((Lr.router=Lr).$get=t).$inject=["$location","$browser","$window","$sniffer","$rootScope","$http","$templateCache"],Lr}jr.$inject=["$locationProvider"];var Hr=function(n){return["$uiRouterProvider",function(e){var t=e.router[n];return t.$get=function(){return t},t}]};function Yr(t,e,n){if(V.$injector=t,V.$q=e,!t.hasOwnProperty("strictDi"))try{t.invoke(function(e){})}catch(e){t.strictDi=!!/strict mode/.exec(e&&e.toString())}n.stateRegistry.get().map(function(e){return e.$$state().resolvables}).reduce(fe,[]).filter(function(e){return"deferred"===e.deps}).forEach(function(e){return e.deps=t.annotate(e.resolveFn,t.strictDi)})}Yr.$inject=["$injector","$q","$uiRouter"];function Nr(e){e.$watch(function(){ut.approximateDigests++})}Nr.$inject=["$rootScope"],Mr.provider("$uiRouter",jr),Ir.provider("$urlRouter",["$uiRouterProvider",function(e){return e.urlRouterProvider=new Pr(e)}]),Rr.provider("$urlService",Hr("urlService")),Rr.provider("$urlMatcherFactory",["$uiRouterProvider",function(){return Lr.urlMatcherFactory}]),Rr.provider("$templateFactory",function(){return new Dr}),Vr.provider("$stateRegistry",Hr("stateRegistry")),Vr.provider("$uiRouterGlobals",Hr("globals")),Vr.provider("$transitions",Hr("transitionService")),Vr.provider("$state",["$uiRouterProvider",function(){return N(Lr.stateProvider,{$get:function(){return Lr.stateService}})}]),Vr.factory("$stateParams",["$uiRouter",function(e){return e.globals.params}]),Fr.factory("$view",function(){return Lr.viewService}),Fr.service("$trace",function(){return ut}),Fr.run(Nr),Rr.run(["$urlMatcherFactory",function(e){}]),Vr.run(["$state",function(e){}]),Ir.run(["$urlRouter",function(e){}]),Mr.run(Yr);var qr,Ur,zr,Br,Wr=function(n){return n.getTokens().filter(O).map(function(e){var t=n.getResolvable(e);return[e,"NOWAIT"===n.getPolicy(t).async?t.promise:t.data]}).reduce(ke,{})};function Gr(e){var t,n=e.match(/^\s*({[^}]*})\s*$/);if(n&&(e="("+n[1]+")"),!(t=e.replace(/\n/g," ").match(/^\s*([^(]*?)\s*(\((.*)\))?\s*$/))||4!==t.length)throw new Error("Invalid state ref '"+e+"'");return{state:t[1]||null,paramExpr:t[3]||null}}function Kr(e){var t=e.parent().inheritedData("$uiView"),n=S("$cfg.path")(t);return n?De(n).state.name:void 0}function Qr(e,t,n){var r,i=n.uiState||e.current.name,o=N((r=e,{relative:Kr(t)||r.$current,inherit:!0,source:"sref"}),n.uiStateOpts||{}),a=e.href(i,n.uiStateParams,o);return{uiState:i,uiStateParams:n.uiStateParams,uiStateOpts:o,href:a}}function Zr(e){var t="[object SVGAnimatedString]"===Object.prototype.toString.call(e.prop("href")),n="FORM"===e[0].nodeName;return{attr:n?"action":t?"xlink:href":"href",isAnchor:"A"===e.prop("tagName").toUpperCase(),clickable:!n}}function Xr(o,a,s,l,u){return function(e){var t=e.which||e.button,n=u();if(!(1>>0;if(0===i)return-1;var o=+t||0;if(Math.abs(o)===1/0&&(o=0),i<=o)return-1;for(n=Math.max(0<=o?o:i-Math.abs(o),0);n
          ',this.loadingBarTemplate='
          ',this.$get=["$injector","$document","$timeout","$rootScope",function(i,o,a,s){function l(e){if(m){var t=100*e+"%";f.css("width",t),v=e,y&&(a.cancel(c),c=a(function(){n()},250))}}function n(){if(!(1<=r())){var e,t=r();e=0<=t&&t<.25?(3*Math.random()+3)/100:.25<=t&&t<.65?3*Math.random()/100:.65<=t&&t<.9?2*Math.random()/100:.9<=t&&t<.99?.005:0,l(r()+e)}}function r(){return v}function t(){v=0,m=!1}var u,c,d,p=this.parentSelector,h=angular.element(this.loadingBarTemplate),f=h.find("div").eq(0),g=angular.element(this.spinnerTemplate),m=!1,v=0,y=this.autoIncrement,w=this.includeSpinner,b=this.includeBar,$=this.startSize;return{start:function(){if(u||(u=i.get("$animate")),a.cancel(d),!m){var e=o[0],t=e.querySelector?e.querySelector(p):o.find(p)[0];t||(t=e.getElementsByTagName("body")[0]);var n=angular.element(t),r=t.lastChild&&angular.element(t.lastChild);s.$broadcast("cfpLoadingBar:started"),m=!0,b&&u.enter(h,n,r),w&&u.enter(g,n,h),l($)}},set:l,status:r,inc:n,complete:function(){u||(u=i.get("$animate")),s.$broadcast("cfpLoadingBar:completed"),l(1),a.cancel(d),d=a(function(){var e=u.leave(h,t);e&&e.then&&e.then(t),u.leave(g)},500)},autoIncrement:this.autoIncrement,includeSpinner:this.includeSpinner,latencyThreshold:this.latencyThreshold,parentSelector:this.parentSelector,startSize:this.startSize}}]})}(),angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,s,l){function e(e){for(var t in e)if(void 0!==n.style[t])return e[t]}var u=function(e,t,n){n=n||{};var r=a.defer(),i=u[n.animation?"animationEndEventName":"transitionEndEventName"],o=function(){l.$apply(function(){e.unbind(i,o),r.resolve(e)})};return i&&e.bind(i,o),s(function(){angular.isString(t)?e.addClass(t):angular.isFunction(t)?t(e):angular.isObject(t)&&e.css(t),i||r.resolve(e)}),r.promise.cancel=function(){i&&e.unbind(i,o),r.reject("Transition cancelled")},r.promise},n=document.createElement("trans");return u.transitionEndEventName=e({WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}),u.animationEndEventName=e({WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"}),u}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(l){return{link:function(e,r,t){function n(e){function t(){a===n&&(a=void 0)}var n=l(r,e);return a&&a.cancel(),(a=n).then(t,t),n}function i(){r.removeClass("collapsing"),r.addClass("collapse in"),r.css({height:"auto"})}function o(){r.removeClass("collapsing"),r.addClass("collapse")}var a,s=!0;e.$watch(t.collapse,function(e){e?s?(s=!1,o(),r.css({height:0})):(r.css({height:r[0].scrollHeight+"px"}),r[0].offsetWidth,r.removeClass("collapse in").addClass("collapsing"),n({height:0}).then(o)):s?(s=!1,i()):(r.removeClass("collapse").addClass("collapsing"),n({height:r[0].scrollHeight+"px"}).then(i))})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(e,n,r){this.groups=[],this.closeOthers=function(t){(angular.isDefined(n.closeOthers)?e.$eval(n.closeOthers):r.closeOthers)&&angular.forEach(this.groups,function(e){e!==t&&(e.isOpen=!1)})},this.addGroup=function(e){var t=this;this.groups.push(e),e.$on("$destroy",function(){t.removeGroup(e)})},this.removeGroup=function(e){var t=this.groups.indexOf(e);-1!==t&&this.groups.splice(t,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(e){this.heading=e}},link:function(t,e,n,r){r.addGroup(t),t.$watch("isOpen",function(e){e&&r.closeOthers(t)}),t.toggleOpen=function(){t.isDisabled||(t.isOpen=!t.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(e,t,n,r,i){r.setHeading(i(e,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(e,t,n,r){e.$watch(function(){return r[n.accordionTransclude]},function(e){e&&(t.html(""),t.append(e))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(e,t){e.closeable="close"in t,this.close=e.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function(i){return{require:"alert",link:function(e,t,n,r){i(function(){r.close()},parseInt(n.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(e,t,n){t.addClass("ng-binding").data("$binding",n.bindHtmlUnsafe),e.$watch(n.bindHtmlUnsafe,function(e){t.html(e||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(e){this.activeClass=e.activeClass||"active",this.toggleEvent=e.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(t,n,r,e){var i=e[0],o=e[1];o.$render=function(){n.toggleClass(i.activeClass,angular.equals(o.$modelValue,t.$eval(r.btnRadio)))},n.bind(i.toggleEvent,function(){var e=n.hasClass(i.activeClass);(!e||angular.isDefined(r.uncheckable))&&t.$apply(function(){o.$setViewValue(e?null:t.$eval(r.btnRadio)),o.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(r,e,t,n){function i(){return o(t.btnCheckboxTrue,!0)}function o(e,t){var n=r.$eval(e);return angular.isDefined(n)?n:t}var a=n[0],s=n[1];s.$render=function(){e.toggleClass(a.activeClass,angular.equals(s.$modelValue,i()))},e.bind(a.toggleEvent,function(){r.$apply(function(){s.$setViewValue(e.hasClass(a.activeClass)?o(t.btnCheckboxFalse,!1):i()),s.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$interval","$transition",function(a,t,n,s){function l(){r();var e=+a.interval;!isNaN(e)&&0=d.length?d[t-1]:d[t]):t
          ");e.attr({"ng-model":"date","ng-change":"dateSelection()"});var c=angular.element(e.children()[0]);i.datepickerOptions&&angular.forEach(r.$parent.$eval(i.datepickerOptions),function(e,t){c.attr(o(t),e)}),r.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(t){if(i[t]){var e=g(i[t]);if(r.$parent.$watch(e,function(e){r.watchData[t]=e}),c.attr(o(t),"watchData."+t),"datepickerMode"===t){var n=e.assign;r.$watch("watchData."+t,function(e,t){e!==t&&n(r.$parent,e)})}}}),i.dateDisabled&&c.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),n.$parsers.unshift(a),r.dateSelection=function(e){angular.isDefined(e)&&(r.date=e),n.$setViewValue(r.date),n.$render(),l&&(r.isOpen=!1,t[0].focus())},t.bind("input change keyup",function(){r.$apply(function(){r.date=n.$modelValue})}),n.$render=function(){var e=n.$viewValue?y(n.$viewValue,s):"";t.val(e),r.date=a(n.$modelValue)};var d=function(e){r.isOpen&&e.target!==t[0]&&r.$apply(function(){r.isOpen=!1})},p=function(e){r.keydown(e)};t.bind("keydown",p),r.keydown=function(e){27===e.which?(e.preventDefault(),e.stopPropagation(),r.close()):40!==e.which||r.isOpen||(r.isOpen=!0)},r.$watch("isOpen",function(e){e?(r.$broadcast("datepicker.focus"),r.position=u?v.offset(t):v.position(t),r.position.top=r.position.top+t.prop("offsetHeight"),m.bind("click",d)):m.unbind("click",d)}),r.select=function(e){if("today"===e){var t=new Date;angular.isDate(n.$modelValue)?(e=new Date(n.$modelValue)).setFullYear(t.getFullYear(),t.getMonth(),t.getDate()):e=new Date(t.setHours(0,0,0,0))}r.dateSelection(e)},r.close=function(){r.isOpen=!1,t[0].focus()};var h=f(e)(r);e.remove(),u?m.find("body").append(h):t.after(h),r.$on("$destroy",function(){h.remove(),t.unbind("keydown",p),m.unbind("click",d)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(e,t){t.bind("click",function(e){e.preventDefault(),e.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(t){var n=null;this.open=function(e){n||(t.bind("click",r),t.bind("keydown",i)),n&&n!==e&&(n.isOpen=!1),n=e},this.close=function(e){n===e&&(n=null,t.unbind("click",r),t.unbind("keydown",i))};var r=function(e){if(n){var t=n.getToggleElement();e&&t&&t[0].contains(e.target)||n.$apply(function(){n.isOpen=!1})}},i=function(e){27===e.which&&(n.focusToggleElement(),r())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(n,t,r,e,i,o){var a,s=this,l=n.$new(),u=e.openClass,c=angular.noop,d=t.onToggle?r(t.onToggle):angular.noop;this.init=function(e){s.$element=e,t.isOpen&&(a=r(t.isOpen),c=a.assign,n.$watch(a,function(e){l.isOpen=!!e}))},this.toggle=function(e){return l.isOpen=arguments.length?!!e:!l.isOpen},this.isOpen=function(){return l.isOpen},l.getToggleElement=function(){return s.toggleElement},l.focusToggleElement=function(){s.toggleElement&&s.toggleElement[0].focus()},l.$watch("isOpen",function(e,t){o[e?"addClass":"removeClass"](s.$element,u),e?(l.focusToggleElement(),i.open(l)):i.close(l),c(n,e),angular.isDefined(e)&&e!==t&&d(n,{open:!!e})}),n.$on("$locationChangeSuccess",function(){l.isOpen=!1}),n.$on("$destroy",function(){l.$destroy()})}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(e,t,n,r){r.init(t)}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(t,n,r,i){if(i){i.toggleElement=n;var e=function(e){e.preventDefault(),n.hasClass("disabled")||r.disabled||t.$apply(function(){i.toggle()})};n.bind("click",e),n.attr({"aria-haspopup":!0,"aria-expanded":!1}),t.$watch(i.isOpen,function(e){n.attr("aria-expanded",!!e)}),t.$on("$destroy",function(){n.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var r=[];return{add:function(e,t){r.push({key:e,value:t})},get:function(e){for(var t=0;t");i.attr("backdrop-class",t.backdropClass),h=c(i)(f),n.append(h)}var o=angular.element("
          ");o.attr({"template-url":t.windowTemplateUrl,"window-class":t.windowClass,size:t.size,index:m.length()-1,animate:"animate"}).html(t.content);var a=c(o)(t.scope);m.top().value.modalDomEl=a,n.append(a),n.addClass(g)},n.close=function(e,t){var n=m.get(e);n&&(n.value.deferred.resolve(t),r(e))},n.dismiss=function(e,t){var n=m.get(e);n&&(n.value.deferred.reject(t),r(e))},n.dismissAll=function(e){for(var t=this.getTop();t;)this.dismiss(t.key,e),t=this.getTop()},n.getTop=function(){return m.top()},n}]).provider("$modal",function(){var g={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(l,u,c,d,p,h,f){var e={};return e.open=function(o){var a=c.defer(),e=c.defer(),s={result:a.promise,opened:e.promise,close:function(e){f.close(s,e)},dismiss:function(e){f.dismiss(s,e)}};if((o=angular.extend({},g.options,o)).resolve=o.resolve||{},!o.template&&!o.templateUrl)throw new Error("One of template or templateUrl options is required.");var t,n,r,i=c.all([(r=o,r.template?c.when(r.template):d.get(angular.isFunction(r.templateUrl)?r.templateUrl():r.templateUrl,{cache:p}).then(function(e){return e.data}))].concat((t=o.resolve,n=[],angular.forEach(t,function(e){(angular.isFunction(e)||angular.isArray(e))&&n.push(c.when(l.invoke(e)))}),n)));return i.then(function(n){var e=(o.scope||u).$new();e.$close=s.close,e.$dismiss=s.dismiss;var t,r={},i=1;o.controller&&(r.$scope=e,r.$modalInstance=s,angular.forEach(o.resolve,function(e,t){r[t]=n[i++]}),t=h(o.controller,r),o.controllerAs&&(e[o.controllerAs]=t)),f.open(s,{scope:e,deferred:a,content:n[0],backdrop:o.backdrop,keyboard:o.keyboard,backdropClass:o.backdropClass,windowClass:o.windowClass,windowTemplateUrl:o.windowTemplateUrl,size:o.size})},function(e){a.reject(e)}),i.then(function(){e.resolve(!0)},function(){e.reject(!1)}),s},e}]};return g}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(n,r,i){var o=this,a={$setViewValue:angular.noop},t=r.numPages?i(r.numPages).assign:angular.noop;this.init=function(e,t){a=e,this.config=t,a.$render=function(){o.render()},r.itemsPerPage?n.$parent.$watch(i(r.itemsPerPage),function(e){o.itemsPerPage=parseInt(e,10),n.totalPages=o.calculateTotalPages()}):this.itemsPerPage=t.itemsPerPage},this.calculateTotalPages=function(){var e=this.itemsPerPage<1?1:Math.ceil(n.totalItems/this.itemsPerPage);return Math.max(e||0,1)},this.render=function(){n.page=parseInt(a.$viewValue,10)||1},n.selectPage=function(e){n.page!==e&&0e?n.selectPage(e):a.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(s,l){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(e,t,n,r){function c(e,t,n){return{number:e,text:t,active:n}}var i=r[0],o=r[1];if(o){var d=angular.isDefined(n.maxSize)?e.$parent.$eval(n.maxSize):l.maxSize,p=angular.isDefined(n.rotate)?e.$parent.$eval(n.rotate):l.rotate;e.boundaryLinks=angular.isDefined(n.boundaryLinks)?e.$parent.$eval(n.boundaryLinks):l.boundaryLinks,e.directionLinks=angular.isDefined(n.directionLinks)?e.$parent.$eval(n.directionLinks):l.directionLinks,i.init(o,l),n.maxSize&&e.$parent.$watch(s(n.maxSize),function(e){d=parseInt(e,10),i.render()});var a=i.render;i.render=function(){a(),0';return{restrict:"EA",compile:function(){var _=o(i);return function(r,t,i){function e(){m.isOpen?o():n()}function n(){var e,t,n;(!g||r.$eval(i[S+"Enable"]))&&(n=i[S+"Placement"],m.placement=angular.isDefined(n)?n:D.placement,e=i[S+"PopupDelay"],t=parseInt(e,10),m.popupDelay=isNaN(t)?D.popupDelay:t,m.popupDelay?p||(p=x(a,m.popupDelay,!1)).then(function(e){e()}):a()())}function o(){r.$apply(function(){s()})}function a(){return p=null,d&&(x.cancel(d),d=null),m.content?(u&&l(),c=m.$new(),(u=_(c,function(e){h?O.find("body").append(e):t.after(e)})).css({top:0,left:0,display:"block"}),m.$digest(),v(),m.isOpen=!0,m.$digest(),v):angular.noop}function s(){m.isOpen=!1,x.cancel(p),p=null,m.animation?d||(d=x(l,500)):l()}function l(){d=null,u&&(u.remove(),u=null),c&&(c.$destroy(),c=null)}var u,c,d,p,h=!!angular.isDefined(D.appendToBody)&&D.appendToBody,f=k(void 0),g=angular.isDefined(i[S+"Enable"]),m=r.$new(!0),v=function(){var e=T.positionElements(t,u,m.placement,h);e.top+="px",e.left+="px",u.css(e)};m.isOpen=!1,i.$observe(C,function(e){!(m.content=e)&&m.isOpen&&s()}),i.$observe(S+"Title",function(e){m.title=e});var y,w=function(){t.unbind(f.show,n),t.unbind(f.hide,o)};y=i[S+"Trigger"],w(),(f=k(y)).show===f.hide?t.bind(f.show,e):(t.bind(f.show,n),t.bind(f.hide,o));var b=r.$eval(i[S+"Animation"]);m.animation=angular.isDefined(b)?!!b:D.animation;var $=r.$eval(i[S+"AppendToBody"]);(h=angular.isDefined($)?$:h)&&r.$on("$locationChangeSuccess",function(){m.isOpen&&s()}),r.$on("$destroy",function(){x.cancel(d),x.cancel(p),w(),l(),m=null})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(e){return e("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(e){return e("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(e){return e("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(n,e,t){var r=this,i=angular.isDefined(e.animate)?n.$parent.$eval(e.animate):t.animate;this.bars=[],n.max=angular.isDefined(e.max)?n.$parent.$eval(e.max):t.max,this.addBar=function(t,e){i||e.css({transition:"none"}),this.bars.push(t),t.$watch("value",function(e){t.percent=+(100*e/n.max).toFixed(2)}),t.$on("$destroy",function(){e=null,r.removeBar(t)})},this.removeBar=function(e){this.bars.splice(this.bars.indexOf(e),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(e,t,n,r){r.addBar(e,t)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(e,t,n,r){r.addBar(e,angular.element(t.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(n,r,i){var o={$setViewValue:angular.noop};this.init=function(e){(o=e).$render=this.render,this.stateOn=angular.isDefined(r.stateOn)?n.$parent.$eval(r.stateOn):i.stateOn,this.stateOff=angular.isDefined(r.stateOff)?n.$parent.$eval(r.stateOff):i.stateOff;var t=angular.isDefined(r.ratingStates)?n.$parent.$eval(r.ratingStates):new Array(angular.isDefined(r.max)?n.$parent.$eval(r.max):i.max);n.range=this.buildTemplateObjects(t)},this.buildTemplateObjects=function(e){for(var t=0,n=e.length;t");v.attr({id:t,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(e.typeaheadTemplateUrl)&&v.attr("template-url",e.typeaheadTemplateUrl);var y=function(){m.matches=[],m.activeIdx=-1,a.attr("aria-expanded",!1)},w=function(e){return t+"-option-"+e};m.$watch("activeIdx",function(e){e<0?a.removeAttr("aria-activedescendant"):a.attr("aria-activedescendant",w(e))});var b=function(r){var i={$viewValue:r};u(o,!0),x.when(g.source(o,i)).then(function(e){var t=r===s.$viewValue;if(t&&l)if(0=n?0$&"):e}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(e){e.put("template/accordion/accordion-group.html",'
          \n
          \n

          \n {{heading}}\n

          \n
          \n
          \n\t
          \n
          \n
          \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(e){e.put("template/accordion/accordion.html",'
          ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(e){e.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(e){e.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(e){e.put("template/carousel/slide.html","
          \n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/datepicker.html",'
          \n \n \n \n
          ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
          {{label.abbr}}
          {{ weekNumbers[$index] }}\n \n
          \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
          \n \n
          \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
          \n \n
          \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(e){e.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(e){e.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(e){e.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(e){e.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(e){e.put("template/tooltip/tooltip-html-unsafe-popup.html",'
          \n
          \n
          \n
          \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(e){e.put("template/tooltip/tooltip-popup.html",'
          \n
          \n
          \n
          \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(e){e.put("template/popover/popover.html",'
          \n
          \n\n
          \n

          \n
          \n
          \n
          \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(e){e.put("template/progressbar/bar.html",'
          ')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(e){e.put("template/progressbar/progress.html",'
          ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(e){e.put("template/progressbar/progressbar.html",'
          \n
          \n
          ')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(e){e.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tab.html",'
        2. \n {{heading}}\n
        3. \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tabset.html",'
          \n \n
          \n
          \n
          \n
          \n
          \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(e){e.put("template/timepicker/timepicker.html",'\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
           
          \n\t\t\t\t\n\t\t\t:\n\t\t\t\t\n\t\t\t
           
          \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(e){e.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(e){e.put("template/typeahead/typeahead-popup.html",'\n')}]),function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function p(){return e.apply(null,arguments)}function s(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function l(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function u(e){return void 0===e}function c(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function h(e,t){var n,r=[];for(n=0;n>>0,r=0;rCe(e)?(o=e+1,s-Ce(e)):(o=e,s),{year:o,dayOfYear:a}}function Ne(e,t,n){var r,i,o=He(e.year(),t,n),a=Math.floor((e.dayOfYear()-o-1)/7)+1;return a<1?r=a+qe(i=e.year()-1,t,n):a>qe(e.year(),t,n)?(r=a-qe(e.year(),t,n),i=e.year()+1):(i=e.year(),r=a),{week:r,year:i}}function qe(e,t,n){var r=He(e,t,n),i=He(e+1,t,n);return(Ce(e)-r+i)/7}N("w",["ww",2],"wo","week"),N("W",["WW",2],"Wo","isoWeek"),P("week","w"),P("isoWeek","W"),V("week",5),V("isoWeek",5),le("w",Q),le("ww",Q,B),le("W",Q),le("WW",Q,B),he(["w","ww","W","WW"],function(e,t,n,r){t[r.substr(0,1)]=S(e)});N("d",0,"do","day"),N("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),N("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),N("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),N("e",0,0,"weekday"),N("E",0,0,"isoWeekday"),P("day","d"),P("weekday","e"),P("isoWeekday","E"),V("day",11),V("weekday",11),V("isoWeekday",11),le("d",Q),le("e",Q),le("E",Q),le("dd",function(e,t){return t.weekdaysMinRegex(e)}),le("ddd",function(e,t){return t.weekdaysShortRegex(e)}),le("dddd",function(e,t){return t.weekdaysRegex(e)}),he(["dd","ddd","dddd"],function(e,t,n,r){var i=n._locale.weekdaysParse(e,r,n._strict);null!=i?t.d=i:v(n).invalidWeekday=e}),he(["d","e","E"],function(e,t,n,r){t[r]=S(e)});var Ue="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var Be="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var We=ae;var Ge=ae;var Ke=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,r,i,o,a=[],s=[],l=[],u=[];for(t=0;t<7;t++)n=m([2e3,1]).day(t),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),o=this.weekdays(n,""),a.push(r),s.push(i),l.push(o),u.push(r),u.push(i),u.push(o);for(a.sort(e),s.sort(e),l.sort(e),u.sort(e),t=0;t<7;t++)s[t]=ce(s[t]),l[t]=ce(l[t]),u[t]=ce(u[t]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Ze(){return this.hours()%12||12}function Xe(e,t){N(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function Je(e,t){return t._meridiemParse}N("H",["HH",2],0,"hour"),N("h",["hh",2],0,Ze),N("k",["kk",2],0,function(){return this.hours()||24}),N("hmm",0,0,function(){return""+Ze.apply(this)+F(this.minutes(),2)}),N("hmmss",0,0,function(){return""+Ze.apply(this)+F(this.minutes(),2)+F(this.seconds(),2)}),N("Hmm",0,0,function(){return""+this.hours()+F(this.minutes(),2)}),N("Hmmss",0,0,function(){return""+this.hours()+F(this.minutes(),2)+F(this.seconds(),2)}),Xe("a",!0),Xe("A",!1),P("hour","h"),V("hour",13),le("a",Je),le("A",Je),le("H",Q),le("h",Q),le("k",Q),le("HH",Q,B),le("hh",Q,B),le("kk",Q,B),le("hmm",Z),le("hmmss",X),le("Hmm",Z),le("Hmmss",X),pe(["H","HH"],ve),pe(["k","kk"],function(e,t,n){var r=S(e);t[ve]=24===r?0:r}),pe(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),pe(["h","hh"],function(e,t,n){t[ve]=S(e),v(n).bigHour=!0}),pe("hmm",function(e,t,n){var r=e.length-2;t[ve]=S(e.substr(0,r)),t[ye]=S(e.substr(r)),v(n).bigHour=!0}),pe("hmmss",function(e,t,n){var r=e.length-4,i=e.length-2;t[ve]=S(e.substr(0,r)),t[ye]=S(e.substr(r,2)),t[we]=S(e.substr(i)),v(n).bigHour=!0}),pe("Hmm",function(e,t,n){var r=e.length-2;t[ve]=S(e.substr(0,r)),t[ye]=S(e.substr(r))}),pe("Hmmss",function(e,t,n){var r=e.length-4,i=e.length-2;t[ve]=S(e.substr(0,r)),t[ye]=S(e.substr(r,2)),t[we]=S(e.substr(i))});var et,tt=xe("Hours",!0),nt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Pe,monthsShort:Me,week:{dow:0,doy:6},weekdays:Ue,weekdaysMin:Be,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},rt={},it={};function ot(e){return e?e.toLowerCase().replace("_","-"):e}function at(e){var t=null;if(!rt[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=et._abbr,require("./locale/"+e),st(t)}catch(e){}return rt[e]}function st(e,t){var n;return e&&((n=u(t)?ut(e):lt(e,t))?et=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),et._abbr}function lt(e,t){if(null===t)return delete rt[e],null;var n,r=nt;if(t.abbr=e,null!=rt[e])x("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=rt[e]._config;else if(null!=t.parentLocale)if(null!=rt[t.parentLocale])r=rt[t.parentLocale]._config;else{if(null==(n=at(t.parentLocale)))return it[t.parentLocale]||(it[t.parentLocale]=[]),it[t.parentLocale].push({name:e,config:t}),null;r=n._config}return rt[e]=new E(T(r,t)),it[e]&&it[e].forEach(function(e){lt(e.name,e.config)}),st(e),rt[e]}function ut(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return et;if(!s(e)){if(t=at(e))return t;e=[e]}return function(e){for(var t,n,r,i,o=0;o=t&&a(i,n,!0)>=t-1)break;t--}o++}return et}(e)}function ct(e){var t,n=e._a;return n&&-2===v(e).overflow&&(t=n[ge]<0||11Ee(n[fe],n[ge])?me:n[ve]<0||24qe(n,o,a)?v(e)._overflowWeeks=!0:null!=l?v(e)._overflowWeekday=!0:(s=Ye(n,r,i,o,a),e._a[fe]=s.year,e._dayOfYear=s.dayOfYear)}(e),null!=e._dayOfYear&&(o=dt(e._a[fe],r[fe]),(e._dayOfYear>Ce(o)||0===e._dayOfYear)&&(v(e)._overflowDayOfYear=!0),n=je(o,0,e._dayOfYear),e._a[ge]=n.getUTCMonth(),e._a[me]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=r[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ve]&&0===e._a[ye]&&0===e._a[we]&&0===e._a[be]&&(e._nextDay=!0,e._a[ve]=0),e._d=(e._useUTC?je:function(e,t,n,r,i,o,a){var s=new Date(e,t,n,r,i,o,a);return e<100&&0<=e&&isFinite(s.getFullYear())&&s.setFullYear(e),s}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ve]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(v(e).weekdayMismatch=!0)}}var ht=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ft=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,gt=/Z|[+-]\d\d(?::?\d\d)?/,mt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],yt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,r,i,o,a,s=e._i,l=ht.exec(s)||ft.exec(s);if(l){for(v(e).iso=!0,t=0,n=mt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},un.isLocal=function(){return!!this.isValid()&&!this._isUTC},un.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},un.isUtc=Ht,un.isUTC=Ht,un.zoneAbbr=function(){return this._isUTC?"UTC":""},un.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},un.dates=n("dates accessor is deprecated. Use date instead.",nn),un.months=n("months accessor is deprecated. Use month instead",Ie),un.years=n("years accessor is deprecated. Use year instead",De),un.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),un.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!u(this._isDSTShifted))return this._isDSTShifted;var e={};if(b(e,this),(e=kt(e))._a){var t=e._isUTC?m(e._a):xt(e._a);this._isDSTShifted=this.isValid()&&0-1/0&&t.selectable&&c[e]){var r=c[e](t.utcDateValue),i=[];if(r.weeks)for(var o=0;on+9,past:u.year()r.indexOf(e.startView))throw new Error("startView must be greater than minView");if(!a.isNumber(e.minuteStep))throw new Error("minuteStep must be numeric");if(e.minuteStep<=0||60<=e.minuteStep)throw new Error("minuteStep must be greater than zero and less than 60");if(null!==e.configureOn&&!a.isString(e.configureOn))throw new Error("configureOn must be a string");if(null!==e.configureOn&&e.configureOn.length<1)throw new Error("configureOn must not be an empty string");if(null!==e.renderOn&&!a.isString(e.renderOn))throw new Error("renderOn must be a string");if(null!==e.renderOn&&e.renderOn.length<1)throw new Error("renderOn must not be an empty string");if(null!==e.modelType&&!a.isString(e.modelType))throw new Error("modelType must be a string");if(null!==e.modelType&&e.modelType.length<1)throw new Error("modelType must not be an empty string");"Date"!==e.modelType&&"moment"!==e.modelType&&"milliseconds"!==e.modelType&&(e.parseFormat=e.modelType);if(null!==e.dropdownSelector&&!a.isString(e.dropdownSelector))throw new Error("dropdownSelector must be a string");null===e.dropdownSelector||"undefined"!=typeof jQuery&&"function"==typeof jQuery().dropdown||(i.error("Please DO NOT specify the dropdownSelector option unless you are using jQuery AND Bootstrap.js. Please include jQuery AND Bootstrap.js, or write code to close the dropdown in the on-set-time callback. \n\nThe dropdownSelector configuration option is being removed because it will not function properly."),delete e.dropdownSelector)}}}a.module("ui.bootstrap.datetimepicker",[]).service("dateTimePickerConfig",function(){var e={bg:{previous:"предишна",next:"следваща"},ca:{previous:"anterior",next:"següent"},da:{previous:"forrige",next:"næste"},de:{previous:"vorige",next:"weiter"},"en-au":{previous:"previous",next:"next"},"en-gb":{previous:"previous",next:"next"},en:{previous:"previous",next:"next"},"es-us":{previous:"atrás",next:"siguiente"},es:{previous:"atrás",next:"siguiente"},fi:{previous:"edellinen",next:"seuraava"},fr:{previous:"précédent",next:"suivant"},hu:{previous:"előző",next:"következő"},it:{previous:"precedente",next:"successivo"},ja:{previous:"前へ",next:"次へ"},ml:{previous:"മുൻപുള്ളത്",next:"അടുത്തത്"},nl:{previous:"vorige",next:"volgende"},pl:{previous:"poprzednia",next:"następna"},"pt-br":{previous:"anteriores",next:"próximos"},pt:{previous:"anterior",next:"próximo"},ro:{previous:"anterior",next:"următor"},ru:{previous:"предыдущая",next:"следующая"},sk:{previous:"predošlá",next:"ďalšia"},sv:{previous:"föregående",next:"nästa"},tr:{previous:"önceki",next:"sonraki"},uk:{previous:"назад",next:"далі"},"zh-cn":{previous:"上一页",next:"下一页"},"zh-tw":{previous:"上一頁",next:"下一頁"}}[b.locale().toLowerCase()];return a.extend({},{configureOn:null,dropdownSelector:null,minuteStep:5,minView:"minute",modelType:"Date",parseFormat:"YYYY-MM-DDTHH:mm:ss.SSSZZ",renderOn:null,startView:"day"},{screenReader:e})}).service("dateTimePickerValidator",t).directive("datetimepicker",e),e.$inject=["dateTimePickerConfig","dateTimePickerValidator"],t.$inject=["$log"]}),angular.module("rzTable",[]),angular.module("rzTable").directive("rzTable",["resizeStorage","$injector","$parse",function(g,i,u){function e(e){}function c(n,r,i){return function(e,t){!0!==i.busy&&void 0!==t&&t!==e&&(d(n),p(n,r,i))}}function d(e){k=!0,l.map(function(e){e.remove()}),l=[]}function p(e,t,n){if(!n.busy){b=$(e).find("th"),v=n.mode,y=!angular.isDefined(n.saveTableSizes)||n.saveTableSizes,w=n.profile;var r=function(t,e){try{var n=e.rzMode?t.mode:"BasicResizer",r=i.get(n);return r}catch(e){return console.error("The resizer "+t.mode+" was not found"),null}}(n,t);r&&(S=new r(e,b,h),y&&(D=g.loadTableSizes(e,n.mode,n.profile)),s=S.handles(b),a=S.ctrlColumns,S.setup(),(o=D)&&($(C).width("auto"),a.each(function(e,t){var n=angular.element(t).scope(),r=n.rzCol||$(t).attr("id"),i=o[r];$(t).css({width:i})}),S.onTableReady()),s.each(function(e,t){!function(e,t,n){var r=$("
          ",{class:e.options.handleClass||"rz-handle"});$(n).prepend(r),l.push(r);var i=S.handleMiddleware(r,n);p=e,h=r,f=i,$(h).mousedown(function(e){k&&(S.onFirstDrag(f,h),S.onTableReady(),k=!1),p.options.onResizeStarted&&p.options.onResizeStarted(f);var t={};S.intervene&&(((t=S.intervene.selector(f)).column=t).orgWidth=$(t).width()),e.preventDefault(),$(h).addClass(p.options.handleClassActive||"rz-handle-active");var n,r,i,o,a,s,l,u,c=e.clientX,d=$(f).width();o=p,a=f,s=c,l=d,u=t,_=function(e){var t=e.clientX,n=t-s,r=S.calculate(l,n);if(!(rt)||(this.fixedColumn.width()<=this.getMinWidth(this.fixedColumn)?(this.bound=t,$(this.fixedColumn).width(this.minWidth),!0):void 0)},e.prototype.onEndDrag=function(){this.bound=!1},e.prototype.calculate=function(e,t){return e-t},e}]),angular.module("rzTable").factory("OverflowResizer",["ResizerModel",function(r){function e(e,t,n){r.call(this,e,t,n)}return(e.prototype=Object.create(r.prototype)).setup=function(){$(this.container).css({overflow:"auto"})},e.prototype.onTableReady=function(){$(this.table).width(1)},e}]),function(e,t){"function"==typeof define&&define.amd?define(["angular"],t):"object"==typeof module&&module.exports?module.exports=t(require("angular")):e.angularClipboard=t(e.angular)}(this,function(i){return i.module("angular-clipboard",[]).factory("clipboard",["$document","$window",function(s,l){return{copyText:function(e,t){var n,r,i=l.pageXOffset||s[0].documentElement.scrollLeft,o=l.pageYOffset||s[0].documentElement.scrollTop,a=(n=e,(r=s[0].createElement("textarea")).style.position="absolute",r.style.fontSize="12pt",r.style.border="0",r.style.padding="0",r.style.margin="0",r.style.left="-10000px",r.style.top=(l.pageYOffset||s[0].documentElement.scrollTop)+"px",r.textContent=n,r);s[0].body.appendChild(a),function(e){try{s[0].body.style.webkitUserSelect="initial";var t=s[0].getSelection();t.removeAllRanges();var n=document.createRange();n.selectNodeContents(e),t.addRange(n),e.select(),e.setSelectionRange(0,999999);try{if(!s[0].execCommand("copy"))throw"failure copy"}finally{t.removeAllRanges()}}finally{s[0].body.style.webkitUserSelect=""}}(a),l.scrollTo(i,o),s[0].body.removeChild(a)},supported:"queryCommandSupported"in s[0]&&s[0].queryCommandSupported("copy")}}]).directive("clipboard",["clipboard",function(r){return{restrict:"A",scope:{onCopied:"&",onError:"&",text:"=",supported:"=?"},link:function(t,n){t.supported=r.supported,n.on("click",function(e){try{r.copyText(t.text,n[0]),i.isFunction(t.onCopied)&&t.$evalAsync(t.onCopied())}catch(e){i.isFunction(t.onError)&&t.$evalAsync(t.onError({err:e}))}})}}}])}),function(e,t){"function"==typeof define&&define.amd?define("sifter",t):"object"==typeof exports?module.exports=t():e.Sifter=t()}(this,function(){var e=function(e,t){this.items=e,this.settings=t||{diacritics:!0}};e.prototype.tokenize=function(e){if(!(e=s(String(e||"").toLowerCase()))||!e.length)return[];var t,n,r,i,o=[],a=e.split(/ +/);for(t=0,n=a.length;t/g,">").replace(/"/g,""")},t={before:function(e,t,n){var r=e[t];e[t]=function(){return n.apply(e,arguments),r.apply(e,arguments)}},after:function(t,e,n){var r=t[e];t[e]=function(){var e=r.apply(t,arguments);return n.apply(t,arguments),e}}},n=function(t,n,e){var r,i=t.trigger,o={};for(r in t.trigger=function(){var e=arguments[0];if(-1===n.indexOf(e))return i.apply(t,arguments);o[e]=arguments},e.apply(t,[]),t.trigger=i,o)o.hasOwnProperty(r)&&i.apply(t,o[r])},f=function(e){var t={};if("selectionStart"in e)t.start=e.selectionStart,t.length=e.selectionEnd-t.start;else if(document.selection){e.focus();var n=document.selection.createRange(),r=document.selection.createRange().text.length;n.moveStart("character",-e.value.length),t.start=n.text.length-r,t.length=r}return t},O=function(p){var h=null,e=function(e,t){var n,r,i,o,a,s,l,u,c,d;(t=t||{},(e=e||window.event||{}).metaKey||e.altKey)||(t.force||!1!==p.data("grow"))&&(n=p.val(),e.type&&"keydown"===e.type.toLowerCase()&&(i=48<=(r=e.keyCode)&&r<=57||65<=r&&r<=90||96<=r&&r<=111||186<=r&&r<=222||32===r,46===r||8===r?(u=f(p[0])).length?n=n.substring(0,u.start)+n.substring(u.start+u.length):8===r&&u.start?n=n.substring(0,u.start-1)+n.substring(u.start+1):46===r&&void 0!==u.start&&(n=n.substring(0,u.start)+n.substring(u.start+1)):i&&(s=e.shiftKey,l=String.fromCharCode(e.keyCode),n+=l=s?l.toUpperCase():l.toLowerCase())),o=p.attr("placeholder"),!n&&o&&(n=o),d=p,(a=((c=n)?(w.$testInput||(w.$testInput=S("").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).appendTo("body")),w.$testInput.text(c),function(e,t,n){var r,i,o={};if(n)for(r=0,i=n.length;r").addClass(g.wrapperClass).addClass(s).addClass(a),t=S("
          ").addClass(g.inputClass).addClass("items").appendTo(e),n=S('').appendTo(t).attr("tabindex",w.is(":disabled")?"-1":f.tabIndex),o=S(g.dropdownParent||e),r=S("
          ").addClass(g.dropdownClass).addClass(a).hide().appendTo(o),i=S("
          ").addClass(g.dropdownContentClass).appendTo(r),(u=w.attr("id"))&&(n.attr("id",u+"-selectized"),S("label[for='"+u+"']").attr("for",u+"-selectized")),f.settings.copyClassesToDropdown&&r.addClass(s),e.css({width:w[0].style.width}),f.plugins.names.length&&(l="plugin-"+f.plugins.names.join(" plugin-"),e.addClass(l),r.addClass(l)),(null===g.maxItems||1[data-selectable]",function(e){e.stopImmediatePropagation()}),r.on("mouseenter","[data-selectable]",function(){return f.onOptionHover.apply(f,arguments)}),r.on("mousedown click","[data-selectable]",function(){return f.onOptionSelect.apply(f,arguments)}),d="mousedown",p="*:not(input)",h=function(){return f.onItemSelect.apply(f,arguments)},(c=t).on(d,p,function(e){for(var t=e.target;t&&t.parentNode!==c[0];)t=t.parentNode;return e.currentTarget=t,h.apply(this,[e])}),O(n),t.on({mousedown:function(){return f.onMouseDown.apply(f,arguments)},click:function(){return f.onClick.apply(f,arguments)}}),n.on({mousedown:function(e){e.stopPropagation()},keydown:function(){return f.onKeyDown.apply(f,arguments)},keyup:function(){return f.onKeyUp.apply(f,arguments)},keypress:function(){return f.onKeyPress.apply(f,arguments)},resize:function(){f.positionDropdown.apply(f,[])},blur:function(){return f.onBlur.apply(f,arguments)},focus:function(){return f.ignoreBlur=!1,f.onFocus.apply(f,arguments)},paste:function(){return f.onPaste.apply(f,arguments)}}),y.on("keydown"+m,function(e){f.isCmdDown=e[$?"metaKey":"ctrlKey"],f.isCtrlDown=e[$?"altKey":"ctrlKey"],f.isShiftDown=e.shiftKey}),y.on("keyup"+m,function(e){e.keyCode===C&&(f.isCtrlDown=!1),16===e.keyCode&&(f.isShiftDown=!1),e.keyCode===_&&(f.isCmdDown=!1)}),y.on("mousedown"+m,function(e){if(f.isFocused){if(e.target===f.$dropdown[0]||e.target.parentNode===f.$dropdown[0])return!1;f.$control.has(e.target).length||e.target===f.$control[0]||f.blur(e.target)}}),v.on(["scroll"+m,"resize"+m].join(" "),function(){f.isOpen&&f.positionDropdown.apply(f,arguments)}),v.on("mousemove"+m,function(){f.ignoreHover=!1}),this.revertSettings={$children:w.children().detach(),tabindex:w.attr("tabindex")},w.attr("tabindex",-1).hide().after(f.$wrapper),S.isArray(g.items)&&(f.setValue(g.items),delete g.items),D&&w.on("invalid"+m,function(e){e.preventDefault(),f.isInvalid=!0,f.refreshState()}),f.updateOriginalInput(),f.refreshItems(),f.refreshState(),f.updatePlaceholder(),f.isSetup=!0,w.is(":disabled")&&f.disable(),f.on("change",this.onChange),w.data("selectize",f),w.addClass("selectized"),f.trigger("initialize"),!0===g.preload&&f.onSearchChange("")},setupTemplates:function(){var n=this.settings.labelField,r=this.settings.optgroupLabelField,e={optgroup:function(e){return'
          '+e.html+"
          "},optgroup_header:function(e,t){return'
          '+t(e[r])+"
          "},option:function(e,t){return'
          '+t(e[n])+"
          "},item:function(e,t){return'
          '+t(e[n])+"
          "},option_create:function(e,t){return'
          Add '+t(e.input)+"
          "}};this.settings.render=S.extend({},e,this.settings.render)},setupCallbacks:function(){var e,t,n={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in n)n.hasOwnProperty(e)&&(t=this.settings[n[e]])&&this.on(e,t)},onClick:function(e){this.isFocused&&this.isOpen||(this.focus(),e.preventDefault())},onMouseDown:function(e){var t=this,n=e.isDefaultPrevented();S(e.target);if(t.isFocused){if(e.target!==t.$control_input[0])return"single"===t.settings.mode?t.isOpen?t.close():t.open():n||t.setActiveItem(null),!1}else n||window.setTimeout(function(){t.focus()},0)},onChange:function(){this.$input.trigger("change")},onPaste:function(e){var i=this;i.isFull()||i.isInputHidden||i.isLocked?e.preventDefault():i.settings.splitOn&&setTimeout(function(){var e=i.$control_input.val();if(e.match(i.settings.splitOn))for(var t=S.trim(e).split(i.settings.splitOn),n=0,r=t.length;n=this.settings.maxItems},updateOriginalInput:function(e){var t,n,r,i,o=this;if(e=e||{},1===o.tagType){for(r=[],t=0,n=o.items.length;t'+s(i)+"");r.length||this.$input.attr("multiple")||r.push(''),o.$input.html(r.join(""))}else o.$input.val(o.getValue()),o.$input.attr("value",o.$input.val());o.isSetup&&(e.silent||o.trigger("change",o.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var e=this.$control_input;this.items.length?e.removeAttr("placeholder"):e.attr("placeholder",this.settings.placeholder),e.triggerHandler("update",{force:!0})}},open:function(){var e=this;e.isLocked||e.isOpen||"multi"===e.settings.mode&&e.isFull()||(e.focus(),e.isOpen=!0,e.refreshState(),e.$dropdown.css({visibility:"hidden",display:"block"}),e.positionDropdown(),e.$dropdown.css({visibility:"visible"}),e.trigger("dropdown_open",e.$dropdown))},close:function(){var e=this,t=e.isOpen;"single"===e.settings.mode&&e.items.length&&(e.hideInput(),e.isBlurring||e.$control_input.blur()),e.isOpen=!1,e.$dropdown.hide(),e.setActiveOption(null),e.refreshState(),t&&e.trigger("dropdown_close",e.$dropdown)},positionDropdown:function(){var e=this.$control,t="body"===this.settings.dropdownParent?e.offset():e.position();t.top+=e.outerHeight(!0),this.$dropdown.css({width:e[0].getBoundingClientRect().width,top:t.top,left:t.left})},clear:function(e){var t=this;t.items.length&&(t.$control.children(":not(input)").remove(),t.items=[],t.lastQuery=null,t.setCaret(0),t.setActiveItem(null),t.updatePlaceholder(),t.updateOriginalInput({silent:e}),t.refreshState(),t.showInput(),t.trigger("clear"))},insertAtCaret:function(e){var t=Math.min(this.caretPos,this.items.length),n=e[0],r=this.buffer||this.$control[0];0===t?r.insertBefore(n,r.firstChild):r.insertBefore(n,r.childNodes[t]),this.setCaret(t+1)},deleteSelection:function(e){var t,n,r,i,o,a,s,l,u,c=this;if(r=e&&8===e.keyCode?-1:1,i=f(c.$control_input[0]),c.$activeOption&&!c.settings.hideSelected&&(s=c.getAdjacentOption(c.$activeOption,-1).attr("data-value")),o=[],c.$activeItems.length){for(u=c.$control.children(".active:"+(0
          '+e.title+'×
          '}},e),n.setup=(t=n.setup,function(){t.apply(n,arguments),n.$dropdown_header=S(e.html(e)),n.$dropdown.prepend(n.$dropdown_header)})}),w.define("optgroup_columns",function(s){var o,l=this;s=S.extend({equalizeWidth:!0,equalizeHeight:!0},s),this.getAdjacentOption=function(e,t){var n=e.closest("[data-group]").find("[data-selectable]"),r=n.index(e)+t;return 0<=r&&r
          ',e=e.firstChild,n.body.appendChild(e),t=u.width=e.offsetWidth-e.clientWidth,n.body.removeChild(e)),t},e=function(){var e,t,n,r,i,o,a;if((t=(a=S("[data-group]",l.$dropdown_content)).length)&&l.$dropdown_content.width()){if(s.equalizeHeight){for(e=n=0;e'+t.label+"",o.setup=(n=r.setup,function(){if(t.append){var i=r.settings.render.item;r.settings.render.item=function(e){return t=i.apply(o,arguments),n=a,r=t.search(/(<\/[^>]+>\s*)$/),t.substring(0,r)+n+t.substring(r);var t,n,r}}n.apply(o,arguments),o.$control.on("click","."+t.className,function(e){if(e.preventDefault(),!r.isLocked){var t=S(e.currentTarget).parent();r.setActiveItem(t),r.deleteSelection()&&r.setCaret(r.items.length)}})})):function(i,t){t.className="remove-single";var n,o=i,a=''+t.label+"";i.setup=(n=o.setup,function(){if(t.append){var e=S(o.$input.context).attr("id"),r=(S("#"+e),o.settings.render.item);o.settings.render.item=function(e){return t=r.apply(i,arguments),n=a,S("").append(t).append(n);var t,n}}n.apply(i,arguments),i.$control.on("click","."+t.className,function(e){e.preventDefault(),o.isLocked||o.clear()})})}(this,e)}),w.define("restore_on_backspace",function(r){var i,e=this;r.text=r.text||function(e){return e[this.settings.labelField]},this.onKeyDown=(i=e.onKeyDown,function(e){var t,n;return 8===e.keyCode&&""===this.$control_input.val()&&!this.$activeItems.length&&0<=(t=this.caretPos-1)&&t",{class:function(){var e=[];return e.push(i.options.state?"on":"off"),i.options.size&&e.push(i.options.size),i.options.disabled&&e.push("disabled"),i.options.readonly&&e.push("readonly"),i.options.indeterminate&&e.push("indeterminate"),i.options.inverse&&e.push("inverse"),i.$element.attr("id")&&e.push("id-"+i.$element.attr("id")),e.map(i._getClass.bind(i)).concat([i.options.baseClass],i._getClasses(i.options.wrapperClass)).join(" ")}}),this.$container=s("
          ",{class:this._getClass("container")}),this.$on=s("",{html:this.options.onText,class:this._getClass("handle-on")+" "+this._getClass(this.options.onColor)}),this.$off=s("",{html:this.options.offText,class:this._getClass("handle-off")+" "+this._getClass(this.options.offColor)}),this.$label=s("",{html:this.options.labelText,class:this._getClass("label")}),this.$element.on("init.bootstrapSwitch",this.options.onInit.bind(this,r)),this.$element.on("switchChange.bootstrapSwitch",function(){for(var e=arguments.length,t=Array(e),n=0;n-n._handleWidth/2;n._dragEnd=!1,n.state(n.options.inverse?!t:t)}else n.state(!n.options.state);n._dragStart=!1}},"mouseleave.bootstrapSwitch":function(){n.$label.trigger("mouseup.bootstrapSwitch")}})}},{key:"_externalLabelHandler",value:function(){var t=this,n=this.$element.closest("label");n.on("click",function(e){e.preventDefault(),e.stopImmediatePropagation(),e.target===n[0]&&t.toggleState()})}},{key:"_formHandler",value:function(){var e=this.$element.closest("form");e.data("bootstrap-switch")||e.on("reset.bootstrapSwitch",function(){window.setTimeout(function(){e.find("input").filter(function(){return s(this).data("bootstrap-switch")}).each(function(){return s(this).bootstrapSwitch("state",this.checked)})},1)}).data("bootstrap-switch",!0)}},{key:"_getClass",value:function(e){return this.options.baseClass+"-"+e}},{key:"_getClasses",value:function(e){return s.isArray(e)?e.map(this._getClass.bind(this)):[this._getClass(e)]}}]),t}();s.fn.bootstrapSwitch=function(o){for(var e=arguments.length,a=Array(1
          ');var r,i=f.overlay?"":" ngdialog-no-overlay";if((d=T('
          ')).html(f.overlay?'
          '+t+"
          ":'
          '+t+"
          "),d.data("$ngDialogOptions",f),c.ngDialogId=u,f.data&&O.isString(f.data)){var o=f.data.replace(/^\s*/,"")[0];c.ngDialogData="{"===o||"["===o?O.fromJson(f.data):new String(f.data),c.ngDialogData.ngDialogId=u}else f.data&&O.isObject(f.data)&&(c.ngDialogData=f.data,c.ngDialogData.ngDialogId=u);(f.className&&d.addClass(f.className),f.appendClassName&&d.addClass(f.appendClassName),f.width&&(h=d[0].querySelector(".ngdialog-content"),O.isString(f.width)?h.style.width=f.width:h.style.width=f.width+"px"),f.height&&(h=d[0].querySelector(".ngdialog-content"),O.isString(f.height)?h.style.height=f.height:h.style.height=f.height+"px"),f.disableAnimation&&d.addClass("ngdialog-disabled-animation"),p=f.appendTo&&O.isString(f.appendTo)?O.element(document.querySelector(f.appendTo)):b.body,$.applyAriaAttributes(d,f),f.preCloseCallback)&&(O.isFunction(f.preCloseCallback)?r=f.preCloseCallback:O.isString(f.preCloseCallback)&&c&&(O.isFunction(c[f.preCloseCallback])?r=c[f.preCloseCallback]:c.$parent&&O.isFunction(c.$parent[f.preCloseCallback])?r=c.$parent[f.preCloseCallback]:m&&O.isFunction(m[f.preCloseCallback])&&(r=m[f.preCloseCallback])),r&&d.data("$ngDialogPreCloseCallback",r));if(c.closeThisDialog=function(e){$.closeDialog(d,e)},f.controller&&(O.isString(f.controller)||O.isArray(f.controller)||O.isFunction(f.controller))){var a;f.controllerAs&&O.isString(f.controllerAs)&&(a=f.controllerAs);var s=w(f.controller,O.extend(n,{$scope:c,$element:d}),!0,a);f.bindToController&&O.extend(s.instance,{ngDialogId:c.ngDialogId,ngDialogData:c.ngDialogData,closeThisDialog:c.closeThisDialog,confirm:c.confirm}),"function"==typeof s?d.data("$ngDialogControllerController",s()):d.data("$ngDialogControllerController",s)}if(v(function(){var e=document.querySelectorAll(".ngdialog");$.deactivateAll(e),g(d)(c);var t=y.innerWidth-b.body.prop("clientWidth");b.html.addClass(f.bodyClassName),b.body.addClass(f.bodyClassName);var n=t-(y.innerWidth-b.body.prop("clientWidth"));0window.innerHeight&&(l=v,t++,e=0);var u=l?0===e?l:l+w:v,c=n+t*(b+s);o.css(o._positionY,u+"px"),"center"==o._positionX?o.css("left",parseInt(window.innerWidth/2-s/2)+"px"):o.css(o._positionX,c+"px"),r[o._positionY+o._positionX]=u+a,0m.maxCount&&0===i&&o.scope().kill(!0),e++}}},i=c(e)(n);i._positionY=h.positionY,i._positionX=h.positionX,i._priority=h.priority,i.addClass(h.type);var o=function(e){("click"===(e=e.originalEvent||e).type||"opacity"===e.propertyName&&1<=e.elapsedTime)&&(n.onClose&&n.$apply(n.onClose(i)),i.remove(),$.splice($.indexOf(i),1),n.$destroy(),r())};h.closeOnClick&&(i.addClass("clickable"),i.bind("click",o)),i.bind("webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",o),angular.isNumber(h.delay)&&u(function(){i.addClass("killed")},h.delay),t("none"),angular.element(document.querySelector(h.container)).append(i);var a=-(parseInt(i[0].offsetHeight)+50);if(i.css(i._positionY,a+"px"),$.push(i),"center"==h.positionX){var s=parseInt(i[0].offsetWidth);i.css("left",parseInt(window.innerWidth/2-s/2)+"px")}u(function(){t("")}),n._templateElement=i,n.kill=function(e){e?(n.onClose&&n.$apply(n.onClose(n._templateElement)),$.splice($.indexOf(n._templateElement),1),n._templateElement.remove(),n.$destroy(),u(r)):n._templateElement.addClass("killed")},u(r),_||(angular.element(g).bind("resize",function(e){u(r)}),_=!0),l.resolve(n)}var l=a.defer();"object"==typeof h&&null!==h||(h={message:h}),h.scope=h.scope?h.scope:o,h.template=h.templateUrl?h.templateUrl:m.templateUrl,h.delay=angular.isUndefined(h.delay)?s:h.delay,h.type=e||h.type||m.type||"",h.positionY=h.positionY?h.positionY:m.positionY,h.positionX=h.positionX?h.positionX:m.positionX,h.replaceMessage=h.replaceMessage?h.replaceMessage:m.replaceMessage,h.onClose=h.onClose?h.onClose:m.onClose,h.closeOnClick=null!==h.closeOnClick&&void 0!==h.closeOnClick?h.closeOnClick:m.closeOnClick,h.container=h.container?h.container:m.container,h.priority=h.priority?h.priority:m.priority;var n=i.get(h.template);return n?t(n):r.get(h.template,{cache:!0}).then(function(e){t(e.data)}).catch(function(e){throw new Error("Template ("+h.template+") could not be loaded. "+e)}),l.promise};return t.primary=function(e){return this(e,"primary")},t.error=function(e){return this(e,"error")},t.success=function(e){return this(e,"success")},t.info=function(e){return this(e,"info")},t.warning=function(e){return this(e,"warning")},t.clearAll=function(){angular.forEach($,function(e){e.addClass("killed")})},t}]}),angular.module("ui-notification").run(["$templateCache",function(e){e.put("angular-ui-notification.html",'

          ')}]),function(){var w="__default";angular.module("angularUtils.directives.dirPagination",[]).directive("dirPaginate",["$compile","$parse","paginationService",function(m,v,y){return{terminal:!0,multiElement:!0,priority:100,compile:function(e,t){var f=t.dirPaginate,n=f.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),r=/\|\s*itemsPerPage\s*:\s*(.*\(\s*\w*\)|([^\)]*?(?=\s+as\s+))|[^\)]*)/;if(null===n[2].match(r))throw"pagination directive: the 'itemsPerPage' filter must be set.";var i=n[2].replace(r,""),g=v(i);o=e,angular.forEach(o,function(e){1===e.nodeType&&angular.element(e).attr("dir-paginate-no-compile",!0)});var o;var a=t.paginationId||w;return y.registerInstance(a),function(e,t,n){var r=v(n.paginationId)(e)||n.paginationId||w;y.registerInstance(r);var i,o,a,s,l,u,c,d=(u=r,c=!!(l=f).match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/),u===w||c?l:l.replace(/(\|\s*itemsPerPage\s*:\s*[^|\s]*)/,"$1 : '"+u+"'"));o=n,a=d,(i=t)[0].hasAttribute("dir-paginate-start")||i[0].hasAttribute("data-dir-paginate-start")?(o.$set("ngRepeatStart",a),i.eq(i.length-1).attr("ng-repeat-end",!0)):o.$set("ngRepeat",a),s=t,angular.forEach(s,function(e){1===e.nodeType&&angular.element(e).removeAttr("dir-paginate-no-compile")}),s.eq(0).removeAttr("dir-paginate-start").removeAttr("dir-paginate").removeAttr("data-dir-paginate-start").removeAttr("data-dir-paginate"),s.eq(s.length-1).removeAttr("dir-paginate-end").removeAttr("data-dir-paginate-end");var p=m(t),h=function(e,t,n){var r;if(t.currentPage)r=v(t.currentPage);else{var i=(n+"__currentPage").replace(/\W/g,"_");e[i]=1,r=v(i)}return r}(e,n,r);y.setCurrentPageParser(r,h,e),void 0!==n.totalItems?(y.setAsyncModeTrue(r),e.$watch(function(){return v(n.totalItems)(e)},function(e){0<=e&&y.setCollectionLength(r,e)})):(y.setAsyncModeFalse(r),e.$watchCollection(function(){return g(e)},function(e){if(e){var t=e instanceof Array?e.length:Object.keys(e).length;y.setCollectionLength(r,t)}})),p(e)}}}}]).directive("dirPaginateNoCompile",function(){return{priority:5e3,terminal:!0}}).directive("dirPaginationControls",["paginationService","paginationTemplate",function(d,n){var p=/^\d+$/,e={restrict:"AE",scope:{maxSize:"=?",onPageChange:"&?",paginationId:"=?",autoHide:"=?"},link:function(r,e,t){var n=t.paginationId||w,i=r.paginationId||t.paginationId||w;if(!d.isRegistered(i)&&!d.isRegistered(n)){var o=i!==w?" (id: "+i+") ":" ";window.console&&console.warn("Pagination directive: the pagination controls"+o+"cannot be used without the corresponding pagination directive, which was not found at link time.")}r.maxSize||(r.maxSize=9);r.autoHide=void 0===r.autoHide||r.autoHide,r.directionLinks=!angular.isDefined(t.directionLinks)||r.$parent.$eval(t.directionLinks),r.boundaryLinks=!!angular.isDefined(t.boundaryLinks)&&r.$parent.$eval(t.boundaryLinks);var a=Math.max(r.maxSize,5);function s(e){if(d.isRegistered(i)&&c(e)){var t=r.pagination.current;r.pages=h(e,d.getCollectionLength(i),d.getItemsPerPage(i),a),r.pagination.current=e,u(),r.onPageChange&&r.onPageChange({newPageNumber:e,oldPageNumber:t})}}function l(){if(d.isRegistered(i)){var e=parseInt(d.getCurrentPage(i))||1;r.pages=h(e,d.getCollectionLength(i),d.getItemsPerPage(i),a),r.pagination.current=e,r.pagination.last=r.pages[r.pages.length-1],r.pagination.last
        4. «
        5. {{ pageNumber }}
        6. »
        7. ')}])}();var com_github_culmat_jsTreeTable=function(){function l(e,r,i){return i=i||"children",$.each(e,function(e,t){!function n(e){e[i]&&$.each(e[i],function(e,t){n(t)}),r(e)}(t)}),e}function t(e,n,o,a){var t=e;n=n||"id",o=o||"parent",a=a||"children";var s=[];$.each(t,function(e,t){s[t[n]]=t});var l=[];return $.each(t,function(e,r){var t=r[o];if($.isArray(t)||(t=[t]),0==t.length)l.push(r);else{var i=!1;$.each(t,function(e,t){var n=s[t];n&&(n[a]||(n[a]=[]),$.inArray(r,n[a])<0&&n[a].push(r),i=!0)}),i||l.push(r)}}),l}function u(e,u,c,d,p,t){u=u||"children",c=c||"id",t=t||{};var n=0,r=$("");$.each(t,function(e,t){"class"==e&&"jsTT"!=t?r.addClass(t):r.attr(e,t)});var i=$(""),o=$(""),h=$("");return r.append(i),i.append(o),r.append(h),d?$.each(d,function(e,t){$(o).append($(""))}):($(o).append($("")),$.each(e[0],function(e,t){e!=u&&e!=c&&$(o).append($(""))})),function o(e,a,s,l){n=Math.max(n,s),$.each(e,function(e,n){var r,t,i;n["data-tt-level"]=s,r=n,t=l,i=$(""),$(i).attr("data-tt-id",r[c]),$(i).attr("data-tt-level",r["data-tt-level"]),r[u]&&0!=r[u].length?$(i).attr("data-tt-isnode",!0):$(i).attr("data-tt-isleaf",!0),t&&$(i).attr("data-tt-parent-id",t[c]),p?p($(i),r):d?$.each(d,function(e,t){$(i).append($(""))}):($(i).append($("")),$.each(r,function(e,t){e!=u&&e!=c&&"data-tt-level"!=e&&$(i).append($(""))})),h.append(i),n[a]&&$.each(n[a],function(e,t){o([t],a,s+1,n)})})}(e,u,1),e[0]&&(e[0].maxLevel=n),r}function n(e,t){return $.each(e,function(e,n){$.each(t,function(e,t){n[t]=$(n).attr(t)})}),e}function c(i){i.addClass("jsTT"),i.expandLevel=function(n){$("tr[data-tt-level]",i).each(function(e){var t=parseInt($(this).attr("data-tt-level"));n-1')):r.prepend($('')),r.prepend($('')),t.trExpand=function(e){if(!(this.trChildren.length<1)){e&&(this.trChildrenVisible=!0,$("#state",this).get(0).src=o);var n=e||this.trChildrenVisible;$.each(this.trChildren,function(e,t){n&&$(t).css("display","table-row"),t.trExpand()})}},t.trCollapse=function(e){this.trChildren.length<1||(e&&(this.trChildrenVisible=!1,$("#state",this).get(0).src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHlJREFUeNrcU1sNgDAQ6wgmcAM2MICGGlg1gJnNzWQcvwQGy1j4oUl/7tH0mpwzM7SgQyO+EZAUWh2MkkzSWhJwuRAlHYsJwEwyvs1gABDuzqoJcTw5qxaIJN0bgQRgIjnlmn1heSO5PE6Y2YXe+5Cr5+h++gs12AcAS6FS+7YOsj4AAAAASUVORK5CYII="),$.each(this.trChildren,function(e,t){$(t).css("display","none"),t.trCollapse()}))},$(t).click(function(){this.trChildrenVisible?this.trCollapse(!0):this.trExpand(!0)})}),i}return{depthFirst:l,makeTree:t,renderTree:u,attr2attr:n,treeTable:c,appendTreetable:function(e,t){(t=t||{}).idAttr=t.idAttr||"id",t.childrenAttr=t.childrenAttr||"children";var n=t.controls||[];t.mountPoint||(t.mountPoint=$("body")),t.depthFirst&&l(e,t.depthFirst,t.childrenAttr);var r=u(e,t.childrenAttr,t.idAttr,t.renderedAttr,t.renderer,t.tableAttributes);c(r),t.replaceContent&&t.mountPoint.html("");var i,o,a=t.initialExpandLevel?parseInt(t.initialExpandLevel):-1;if(a=Math.min(a,e[0].maxLevel),r.expandLevel(a),t.slider){var s=$('
          ');s.width("200px"),s.slider({min:1,max:e[0].maxLevel,range:"min",value:a,slide:function(e,t){r.expandLevel(t.value)}}),n=[s].concat(t.controls)}return 0"),$.each(i,function(e,t){o.append($('
          "+t+""+c+""+e+"
          "+r[e]+""+r[c]+""+t+"').append(t))}),$('').append(o))),t.mountPoint.append(r),r},jsTreeTable:"1.0",register:function(n){$.each(this,function(e,t){"register"!=e&&(n[e]=t)})}}}(); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/gulpfile.js ================================================ const gulp = require('gulp'); const plugins = require('gulp-load-plugins')(); const open = require('open'); const app = { srcPath: 'app/', // 源代码 devPath: 'tmp/', // 开发打包 prdPath: 'dist/' // 生产打包 }; const JS_LIBS = [ 'node_modules/angular-ui-router/release/angular-ui-router.js', 'node_modules/oclazyload/dist/ocLazyLoad.min.js', 'node_modules/angular-loading-bar/build/loading-bar.min.js', 'node_modules/angular-bootstrap/ui-bootstrap-tpls.min.js', 'node_modules/moment/moment.js', 'node_modules/angular-date-time-input/src/dateTimeInput.js', 'node_modules/angularjs-bootstrap-datetimepicker/src/js/datetimepicker.js', 'node_modules/angular-table-resize/dist/angular-table-resize.min.js', 'node_modules/angular-clipboard/angular-clipboard.js', 'node_modules/selectize/dist/js/standalone/selectize.js', 'node_modules/angular-selectize2/dist/selectize.js', 'node_modules/bootstrap-switch/dist/js/bootstrap-switch.min.js', 'node_modules/ng-dialog/js/ngDialog.js', 'node_modules/angular-ui-notification/dist/angular-ui-notification.min.js', 'node_modules/angular-utils-pagination/dirPagination.js', 'app/scripts/libs/treeTable.js', ]; const CSS_APP = [ 'node_modules/angular-loading-bar/build/loading-bar.min.css', 'node_modules/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css', 'node_modules/ng-dialog/css/ngDialog.min.css', 'node_modules/ng-dialog/css/ngDialog-theme-default.css', 'node_modules/angularjs-bootstrap-datetimepicker/src/css/datetimepicker.css', 'node_modules/angular-ui-notification/dist/angular-ui-notification.min.css', 'node_modules/angular-table-resize/dist/angular-table-resize.css', 'node_modules/selectize/dist/css/selectize.css', 'app/styles/page.css', 'app/styles/timeline.css', 'app/styles/main.css' ]; const JS_APP = [ 'app/scripts/app.js', 'app/scripts/filters/filters.js', 'app/scripts/services/version_service.js', 'app/scripts/services/auth_service.js', 'app/scripts/services/appservice.js', 'app/scripts/services/flow_service_v1.js', 'app/scripts/services/flow_service_v2.js', 'app/scripts/services/degrade_service.js', 'app/scripts/services/systemservice.js', 'app/scripts/services/machineservice.js', 'app/scripts/services/identityservice.js', 'app/scripts/services/metricservice.js', 'app/scripts/services/param_flow_service.js', 'app/scripts/services/authority_service.js', 'app/scripts/services/cluster_state_service.js', 'app/scripts/services/gateway/api_service.js', 'app/scripts/services/gateway/flow_service.js', ]; gulp.task('lib', function () { gulp.src(JS_LIBS) .pipe(plugins.concat('app.vendor.js')) .pipe(gulp.dest(app.devPath + 'js')) .pipe(plugins.uglify()) .pipe(gulp.dest(app.prdPath + 'js')) .pipe(plugins.connect.reload()); }); /* * css任务 * 在src下创建style文件夹,里面存放less文件。 */ gulp.task('css', function () { gulp.src(CSS_APP) .pipe(plugins.concat('app.css')) .pipe(gulp.dest(app.devPath + 'css')) .pipe(plugins.cssmin()) .pipe(gulp.dest(app.prdPath + 'css')) .pipe(plugins.connect.reload()); }); /* * js任务 * 在src目录下创建script文件夹,里面存放所有的js文件 */ gulp.task('js', function () { gulp.src(JS_APP) .pipe(plugins.concat('app.js')) .pipe(gulp.dest(app.devPath + 'js')) .pipe(plugins.uglify()) .pipe(gulp.dest(app.prdPath + 'js')) .pipe(plugins.connect.reload()); }); /* * js任务 * 在src目录下创建script文件夹,里面存放所有的js文件 */ gulp.task('jshint', function () { gulp.src(JS_APP) .pipe(plugins.jshint()) .pipe(plugins.jshint.reporter()); }); // 每次发布的时候,可能需要把之前目录内的内容清除,避免旧的文件对新的容有所影响。 需要在每次发布前删除dist和build目录 gulp.task('clean', function () { gulp.src([app.devPath, app.prdPath]) .pipe(plugins.clean()); }); // 总任务 gulp.task('build', ['clean', 'jshint', 'lib', 'js', 'css']); // 服务 gulp.task('serve', ['build'], function () { plugins.connect.server({ //启动一个服务器 root: [app.devPath], // 服务器从哪个路径开始读取,默认从开发路径读取 livereload: true, // 自动刷新 port: 1234 }); // 打开浏览器 setTimeout(() => { open('http://localhost:8080/index_dev.htm') }, 200); // 监听 gulp.watch(app.srcPath + '**/*.js', ['js']); gulp.watch(app.srcPath + '**/*.css', ['css']); }); // 定义default任务 gulp.task('default', ['serve']); ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/index.htm ================================================ Sentinel Dashboard
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/index_dev.htm ================================================ Sentinel 控制台
          ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/license-stat.csv ================================================ Type,Package,License npm,angular,MIT License npm,angular-animate,MIT License npm,angular-bootstrap,MIT License npm,angular-clipboard,MIT License npm,angular-cookies,MIT License npm,angular-date-time-input,MIT License npm,angular-loading-bar,MIT License npm,angular-mocks,MIT License npm,angular-resource,MIT License npm,angular-route,MIT License npm,angular-selectize2,MIT License npm,angular-table-resize,MIT License npm,angular-touch,MIT License npm,angular-ui-notification,MIT License npm,angular-ui-router,MIT License npm,angular-utils-pagination,MIT License npm,angularjs-bootstrap-datetimepicker,MIT License npm,bootstrap-switch,Apache License 2.0 npm,bootstrap-tagsinput,MIT License npm,moment,MIT License npm,ng-dialog,MIT License npm,ng-tags-input,MIT License npm,oclazyload,MIT License npm,selectize,Apache License 2.0 lib,jsTreeTable,MIT License ================================================ FILE: sentinel-dashboard/src/main/webapp/resources/package.json ================================================ { "name": "sentinel-dashboard", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo no test case", "build": "gulp build", "start": "gulp" }, "author": "x-cold ", "license": "MIT", "dependencies": { "angular": "^1.4.8", "angular-animate": "^1.4.0", "angular-bootstrap": "^0.12.2", "angular-clipboard": "^1.6.2", "angular-cookies": "^1.4.0", "angular-date-time-input": "^1.2.1", "angular-loading-bar": "^0.9.0", "angular-mocks": "^1.4.0", "angular-resource": "^1.4.0", "angular-route": "^1.4.0", "angular-selectize2": "^v1.2.3", "angular-table-resize": "^2.0.1", "angular-touch": "^1.4.0", "angular-ui-notification": "^0.3.6", "angular-ui-router": "^1.0.18", "angular-utils-pagination": "^0.11.1", "angularjs-bootstrap-datetimepicker": "^1.1.4", "bootstrap-switch": "^3.3.4", "bootstrap-tagsinput": "~0.7.1", "lodash": "^4.17.15", "moment": "^2.12.0", "ng-dialog": "^0.6.6", "ng-tags-input": "~3.0.0", "oclazyload": "^1.1.0", "selectize": "^0.12.1" }, "devDependencies": { "gulp": "^3.9.1", "gulp-clean": "^0.4.0", "gulp-concat": "^2.6.1", "gulp-connect": "^5.7.0", "gulp-csscomb": "^3.0.8", "gulp-cssmin": "^0.2.0", "gulp-jshint": "^2.1.0", "gulp-load-plugins": "^1.6.0", "gulp-serv": "0.0.1", "gulp-uglify": "^3.0.0", "jshint": "^2.10.2", "open": "^6.3.0", "source-map": "^0.7.3" } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClientTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.http.HttpException; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.protocol.RequestContent; import org.junit.Test; public class SentinelApiClientTest { @Test public void postRequest() throws HttpException, IOException { // Processor is required because it will determine the final request body including // headers before outgoing. RequestContent processor = new RequestContent(); Map params = new HashMap(); params.put("a", "1"); params.put("b", "2+"); params.put("c", "3 "); HttpUriRequest request; request = SentinelApiClient.postRequest("/test", params, false); assertNotNull(request); processor.process(request, null); assertNotNull(request.getFirstHeader("Content-Type")); assertEquals("application/x-www-form-urlencoded", request.getFirstHeader("Content-Type").getValue()); request = SentinelApiClient.postRequest("/test", params, true); assertNotNull(request); processor.process(request, null); assertNotNull(request.getFirstHeader("Content-Type")); assertEquals("application/x-www-form-urlencoded; charset=UTF-8", request.getFirstHeader("Content-Type").getValue()); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/config/DashboardConfigTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import static org.junit.Assert.assertEquals; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.EnvironmentVariables; public class DashboardConfigTest { @Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); @Test public void testGetConfigStr() { // clear cache DashboardConfig.clearCache(); // if not set, return null assertEquals(null, DashboardConfig.getConfigStr("a")); // test property System.setProperty("a", "111"); assertEquals("111", DashboardConfig.getConfigStr("a")); // test env environmentVariables.set("a", "222"); // return value in cache assertEquals("111", DashboardConfig.getConfigStr("a")); // clear cache and then test DashboardConfig.clearCache(); assertEquals("222", DashboardConfig.getConfigStr("a")); } @Test public void testGetConfigInt() { // clear cache DashboardConfig.clearCache(); // default value assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); DashboardConfig.clearCache(); assertEquals(1, DashboardConfig.getConfigInt("t", 1, 10)); // property, wrong format System.setProperty("t", "asdf"); DashboardConfig.clearCache(); assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); System.setProperty("t", ""); DashboardConfig.clearCache(); assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); // min value System.setProperty("t", "2"); DashboardConfig.clearCache(); assertEquals(2, DashboardConfig.getConfigInt("t", 0, 1)); DashboardConfig.clearCache(); assertEquals(10, DashboardConfig.getConfigInt("t", 0, 10)); DashboardConfig.clearCache(); assertEquals(2, DashboardConfig.getConfigInt("t", 0, -1)); // env environmentVariables.set("t", "20"); DashboardConfig.clearCache(); assertEquals(20, DashboardConfig.getConfigInt("t", 0, 10)); // wrong format env var, but it will override property environmentVariables.set("t", "20dddd"); DashboardConfig.clearCache(); assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); // clear env, it will take property environmentVariables.set("t", ""); DashboardConfig.clearCache(); assertEquals(10, DashboardConfig.getConfigInt("t", 0, 10)); DashboardConfig.clearCache(); assertEquals(2, DashboardConfig.getConfigInt("t", 0, 1)); // enable cache System.setProperty("t", "666"); DashboardConfig.clearCache(); assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1)); System.setProperty("t", "777"); assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1)); System.setProperty("t", "555"); assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1)); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/config/NoAuthConfigurationTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.config; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import javax.servlet.http.HttpServletRequest; /** * disable auth in test. * * @author wxq */ @TestConfiguration @Import(AuthConfiguration.class) public class NoAuthConfigurationTest { @Bean @Primary public AuthService httpServletRequestNoopAuthService() { return new FakeAuthServiceImpl(); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayApiControllerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.config.NoAuthConfigurationTest; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.time.DateUtils; 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.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.BDDMockito.verify; /** * Test cases for {@link GatewayApiController}. * * @author cdfive */ @RunWith(SpringRunner.class) @WebMvcTest(GatewayApiController.class) @Import({NoAuthConfigurationTest.class, InMemApiDefinitionStore.class, AppManagement.class, SimpleMachineDiscovery.class}) public class GatewayApiControllerTest { private static final String TEST_APP = "test_app"; private static final String TEST_IP = "localhost"; private static final Integer TEST_PORT = 8719; @Autowired private MockMvc mockMvc; @Autowired private InMemApiDefinitionStore repository; @MockBean private SentinelApiClient sentinelApiClient; @Before public void before() { repository.clearAll(); } @Test public void testQueryApis() throws Exception { String path = "/gateway/api/list.json"; List entities = new ArrayList<>(); // Mock two entities ApiDefinitionEntity entity = new ApiDefinitionEntity(); entity.setId(1L); entity.setApp(TEST_APP); entity.setIp(TEST_IP); entity.setPort(TEST_PORT); entity.setApiName("foo"); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); Set itemEntities = new LinkedHashSet<>(); entity.setPredicateItems(itemEntities); ApiPredicateItemEntity itemEntity = new ApiPredicateItemEntity(); itemEntity.setPattern("/aaa"); itemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT); itemEntities.add(itemEntity); entities.add(entity); ApiDefinitionEntity entity2 = new ApiDefinitionEntity(); entity2.setId(2L); entity2.setApp(TEST_APP); entity2.setIp(TEST_IP); entity2.setPort(TEST_PORT); entity2.setApiName("biz"); entity.setGmtCreate(date); entity.setGmtModified(date); Set itemEntities2 = new LinkedHashSet<>(); entity2.setPredicateItems(itemEntities2); ApiPredicateItemEntity itemEntity2 = new ApiPredicateItemEntity(); itemEntity2.setPattern("/bbb"); itemEntity2.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX); itemEntities2.add(itemEntity2); entities.add(entity2); CompletableFuture> completableFuture = mock(CompletableFuture.class); given(completableFuture.get()).willReturn(entities); given(sentinelApiClient.fetchApis(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path); requestBuilder.param("app", TEST_APP); requestBuilder.param("ip", TEST_IP); requestBuilder.param("port", String.valueOf(TEST_PORT)); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the fetchApis method has been called verify(sentinelApiClient).fetchApis(TEST_APP, TEST_IP, TEST_PORT); // Verify if two same entities are got Result> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>>(){}); assertTrue(result.isSuccess()); List data = result.getData(); assertEquals(2, data.size()); assertEquals(entities, data); // Verify the entities are add into memory repository List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(2, entitiesInMem.size()); assertEquals(entities, entitiesInMem); } @Test public void testAddApi() throws Exception { String path = "/gateway/api/new.json"; AddApiReqVo reqVo = new AddApiReqVo(); reqVo.setApp(TEST_APP); reqVo.setIp(TEST_IP); reqVo.setPort(TEST_PORT); reqVo.setApiName("customized_api"); List itemVos = new ArrayList<>(); ApiPredicateItemVo itemVo = new ApiPredicateItemVo(); itemVo.setMatchStrategy(URL_MATCH_STRATEGY_EXACT); itemVo.setPattern("/product"); itemVos.add(itemVo); reqVo.setPredicateItems(itemVos); given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyApis method has been called verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {}); assertTrue(result.isSuccess()); // Verify the result ApiDefinitionEntity entity = result.getData(); assertNotNull(entity); assertEquals(TEST_APP, entity.getApp()); assertEquals(TEST_IP, entity.getIp()); assertEquals(TEST_PORT, entity.getPort()); assertEquals("customized_api", entity.getApiName()); assertNotNull(entity.getId()); assertNotNull(entity.getGmtCreate()); assertNotNull(entity.getGmtModified()); Set predicateItemEntities = entity.getPredicateItems(); assertEquals(1, predicateItemEntities.size()); ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next(); assertEquals(URL_MATCH_STRATEGY_EXACT, predicateItemEntity.getMatchStrategy().intValue()); assertEquals("/product", predicateItemEntity.getPattern()); // Verify the entity which is add in memory repository List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(1, entitiesInMem.size()); assertEquals(entity, entitiesInMem.get(0)); } @Test public void testUpdateApi() throws Exception { String path = "/gateway/api/save.json"; // Add one entity to memory repository for update ApiDefinitionEntity addEntity = new ApiDefinitionEntity(); addEntity.setApp(TEST_APP); addEntity.setIp(TEST_IP); addEntity.setPort(TEST_PORT); addEntity.setApiName("bbb"); Date date = new Date(); // To make the gmtModified different when do update date = DateUtils.addSeconds(date, -1); addEntity.setGmtCreate(date); addEntity.setGmtModified(date); Set addRedicateItemEntities = new HashSet<>(); addEntity.setPredicateItems(addRedicateItemEntities); ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity(); addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT); addPredicateItemEntity.setPattern("/order"); addEntity = repository.save(addEntity); UpdateApiReqVo reqVo = new UpdateApiReqVo(); reqVo.setId(addEntity.getId()); reqVo.setApp(TEST_APP); List itemVos = new ArrayList<>(); ApiPredicateItemVo itemVo = new ApiPredicateItemVo(); itemVo.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX); itemVo.setPattern("/my_order"); itemVos.add(itemVo); reqVo.setPredicateItems(itemVos); given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyApis method has been called verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {}); assertTrue(result.isSuccess()); ApiDefinitionEntity entity = result.getData(); assertNotNull(entity); assertEquals("bbb", entity.getApiName()); assertEquals(date, entity.getGmtCreate()); // To make sure gmtModified has been set and it's different from gmtCreate assertNotNull(entity.getGmtModified()); assertNotEquals(entity.getGmtCreate(), entity.getGmtModified()); Set predicateItemEntities = entity.getPredicateItems(); assertEquals(1, predicateItemEntities.size()); ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next(); assertEquals(URL_MATCH_STRATEGY_PREFIX, predicateItemEntity.getMatchStrategy().intValue()); assertEquals("/my_order", predicateItemEntity.getPattern()); // Verify the entity which is update in memory repository List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(1, entitiesInMem.size()); assertEquals(entity, entitiesInMem.get(0)); } @Test public void testDeleteApi() throws Exception { String path = "/gateway/api/delete.json"; // Add one entity into memory repository for delete ApiDefinitionEntity addEntity = new ApiDefinitionEntity(); addEntity.setApp(TEST_APP); addEntity.setIp(TEST_IP); addEntity.setPort(TEST_PORT); addEntity.setApiName("ccc"); Date date = new Date(); addEntity.setGmtCreate(date); addEntity.setGmtModified(date); Set addRedicateItemEntities = new HashSet<>(); addEntity.setPredicateItems(addRedicateItemEntities); ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity(); addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT); addPredicateItemEntity.setPattern("/user/add"); addEntity = repository.save(addEntity); given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.param("id", String.valueOf(addEntity.getId())); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyApis method has been called verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); // Verify the result Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {}); assertTrue(result.isSuccess()); assertEquals(addEntity.getId(), result.getData()); // Now no entities in memory List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(0, entitiesInMem.size()); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleControllerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.config.NoAuthConfigurationTest; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.time.DateUtils; 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.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.CONTROL_BEHAVIOR_DEFAULT; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.FLOW_GRADE_QPS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.BDDMockito.verify; /** * Test cases for {@link GatewayFlowRuleController}. * * @author cdfive */ @RunWith(SpringRunner.class) @WebMvcTest(GatewayFlowRuleController.class) @Import({NoAuthConfigurationTest.class, InMemGatewayFlowRuleStore.class, AppManagement.class, SimpleMachineDiscovery.class}) public class GatewayFlowRuleControllerTest { private static final String TEST_APP = "test_app"; private static final String TEST_IP = "localhost"; private static final Integer TEST_PORT = 8719; @Autowired private MockMvc mockMvc; @Autowired private InMemGatewayFlowRuleStore repository; @MockBean private SentinelApiClient sentinelApiClient; @Before public void before() { repository.clearAll(); } @Test public void testQueryFlowRules() throws Exception { String path = "/gateway/flow/list.json"; List entities = new ArrayList<>(); // Mock two entities GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity(); entity.setId(1L); entity.setApp(TEST_APP); entity.setIp(TEST_IP); entity.setPort(TEST_PORT); entity.setResource("httpbin_route"); entity.setResourceMode(RESOURCE_MODE_ROUTE_ID); entity.setGrade(FLOW_GRADE_QPS); entity.setCount(5D); entity.setInterval(30L); entity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND); entity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT); entity.setBurst(0); entity.setMaxQueueingTimeoutMs(0); GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); itemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP); entities.add(entity); GatewayFlowRuleEntity entity2 = new GatewayFlowRuleEntity(); entity2.setId(2L); entity2.setApp(TEST_APP); entity2.setIp(TEST_IP); entity2.setPort(TEST_PORT); entity2.setResource("some_customized_api"); entity2.setResourceMode(RESOURCE_MODE_CUSTOM_API_NAME); entity2.setCount(30D); entity2.setInterval(2L); entity2.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE); entity2.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT); entity2.setBurst(0); entity2.setMaxQueueingTimeoutMs(0); GatewayParamFlowItemEntity itemEntity2 = new GatewayParamFlowItemEntity(); entity2.setParamItem(itemEntity2); itemEntity2.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP); entities.add(entity2); CompletableFuture> completableFuture = mock(CompletableFuture.class); given(completableFuture.get()).willReturn(entities); given(sentinelApiClient.fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path); requestBuilder.param("app", TEST_APP); requestBuilder.param("ip", TEST_IP); requestBuilder.param("port", String.valueOf(TEST_PORT)); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the fetchGatewayFlowRules method has been called verify(sentinelApiClient).fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT); // Verify if two same entities are got Result> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>>(){}); assertTrue(result.isSuccess()); List data = result.getData(); assertEquals(2, data.size()); assertEquals(entities, data); // Verify the entities are add into memory repository List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(2, entitiesInMem.size()); assertEquals(entities, entitiesInMem); } @Test public void testAddFlowRule() throws Exception { String path = "/gateway/flow/new.json"; AddFlowRuleReqVo reqVo = new AddFlowRuleReqVo(); reqVo.setApp(TEST_APP); reqVo.setIp(TEST_IP); reqVo.setPort(TEST_PORT); reqVo.setResourceMode(RESOURCE_MODE_ROUTE_ID); reqVo.setResource("httpbin_route"); reqVo.setGrade(FLOW_GRADE_QPS); reqVo.setCount(5D); reqVo.setInterval(30L); reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND); reqVo.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT); reqVo.setBurst(0); reqVo.setMaxQueueingTimeoutMs(0); given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyGatewayFlowRules method has been called verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {}); assertTrue(result.isSuccess()); // Verify the result GatewayFlowRuleEntity entity = result.getData(); assertNotNull(entity); assertEquals(TEST_APP, entity.getApp()); assertEquals(TEST_IP, entity.getIp()); assertEquals(TEST_PORT, entity.getPort()); assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue()); assertEquals("httpbin_route", entity.getResource()); assertNotNull(entity.getId()); assertNotNull(entity.getGmtCreate()); assertNotNull(entity.getGmtModified()); // Verify the entity which is add in memory repository List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(1, entitiesInMem.size()); assertEquals(entity, entitiesInMem.get(0)); } @Test public void testUpdateFlowRule() throws Exception { String path = "/gateway/flow/save.json"; // Add one entity into memory repository for update GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity(); addEntity.setId(1L); addEntity.setApp(TEST_APP); addEntity.setIp(TEST_IP); addEntity.setPort(TEST_PORT); addEntity.setResource("httpbin_route"); addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID); addEntity.setGrade(FLOW_GRADE_QPS); addEntity.setCount(5D); addEntity.setInterval(30L); addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND); addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT); addEntity.setBurst(0); addEntity.setMaxQueueingTimeoutMs(0); Date date = new Date(); // To make the gmtModified different when do update date = DateUtils.addSeconds(date, -1); addEntity.setGmtCreate(date); addEntity.setGmtModified(date); GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity(); addEntity.setParamItem(addItemEntity); addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP); repository.save(addEntity); UpdateFlowRuleReqVo reqVo = new UpdateFlowRuleReqVo(); reqVo.setId(addEntity.getId()); reqVo.setApp(TEST_APP); reqVo.setGrade(FLOW_GRADE_QPS); reqVo.setCount(6D); reqVo.setInterval(2L); reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE); reqVo.setControlBehavior(CONTROL_BEHAVIOR_RATE_LIMITER); reqVo.setMaxQueueingTimeoutMs(500); GatewayParamFlowItemVo itemVo = new GatewayParamFlowItemVo(); reqVo.setParamItem(itemVo); itemVo.setParseStrategy(PARAM_PARSE_STRATEGY_URL_PARAM); itemVo.setFieldName("pa"); given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyGatewayFlowRules method has been called verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() { }); assertTrue(result.isSuccess()); GatewayFlowRuleEntity entity = result.getData(); assertNotNull(entity); assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue()); assertEquals("httpbin_route", entity.getResource()); assertEquals(6D, entity.getCount().doubleValue(), 0); assertEquals(2L, entity.getInterval().longValue()); assertEquals(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE, entity.getIntervalUnit().intValue()); assertEquals(CONTROL_BEHAVIOR_RATE_LIMITER, entity.getControlBehavior().intValue()); assertEquals(0, entity.getBurst().intValue()); assertEquals(500, entity.getMaxQueueingTimeoutMs().intValue()); assertEquals(date, entity.getGmtCreate()); // To make sure gmtModified has been set and it's different from gmtCreate assertNotNull(entity.getGmtModified()); assertNotEquals(entity.getGmtCreate(), entity.getGmtModified()); // Verify the entity which is update in memory repository GatewayParamFlowItemEntity itemEntity = entity.getParamItem(); assertEquals(PARAM_PARSE_STRATEGY_URL_PARAM, itemEntity.getParseStrategy().intValue()); assertEquals("pa", itemEntity.getFieldName()); } @Test public void testDeleteFlowRule() throws Exception { String path = "/gateway/flow/delete.json"; // Add one entity into memory repository for delete GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity(); addEntity.setId(1L); addEntity.setApp(TEST_APP); addEntity.setIp(TEST_IP); addEntity.setPort(TEST_PORT); addEntity.setResource("httpbin_route"); addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID); addEntity.setGrade(FLOW_GRADE_QPS); addEntity.setCount(5D); addEntity.setInterval(30L); addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND); addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT); addEntity.setBurst(0); addEntity.setMaxQueueingTimeoutMs(0); Date date = new Date(); date = DateUtils.addSeconds(date, -1); addEntity.setGmtCreate(date); addEntity.setGmtModified(date); GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity(); addEntity.setParamItem(addItemEntity); addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP); repository.save(addEntity); given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true); MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path); requestBuilder.param("id", String.valueOf(addEntity.getId())); // Do controller logic MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn(); // Verify the modifyGatewayFlowRules method has been called verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any()); // Verify the result Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {}); assertTrue(result.isSuccess()); assertEquals(addEntity.getId(), result.getData()); // Now no entities in memory List entitiesInMem = repository.findAllByApp(TEST_APP); assertEquals(0, entitiesInMem.size()); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/JsonSerializeTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.fastjson.JSON; import org.junit.Assert; import org.junit.Test; /** * @author lianglin * @since 1.7.0 */ public class JsonSerializeTest { @Test public void authorityRuleJsonSerializeTest() { AuthorityRuleEntity emptyRule = new AuthorityRuleEntity(); Assert.assertTrue("{}".equals(JSON.toJSONString(emptyRule))); AuthorityRuleEntity authorityRule = new AuthorityRuleEntity(); AuthorityRule rule = new AuthorityRule(); rule.setStrategy(0).setLimitApp("default").setResource("rs"); authorityRule.setRule(rule); Assert.assertTrue("{\"rule\":{\"limitApp\":\"default\",\"resource\":\"rs\",\"strategy\":0}}".equals(JSON.toJSONString(authorityRule))); } @Test public void paramFlowRuleSerializeTest() { ParamFlowRuleEntity emptyRule = new ParamFlowRuleEntity(); Assert.assertTrue("{}".equals(JSON.toJSONString(emptyRule))); ParamFlowRuleEntity paramFlowRule = new ParamFlowRuleEntity(); ParamFlowRule rule = new ParamFlowRule(); rule.setClusterConfig(new ParamFlowClusterConfig()); rule.setResource("rs").setLimitApp("default"); paramFlowRule.setRule(rule); Assert.assertTrue("{\"rule\":{\"burstCount\":0,\"clusterConfig\":{\"fallbackToLocalWhenFail\":false,\"sampleCount\":10,\"thresholdType\":0,\"windowIntervalMs\":1000},\"clusterMode\":false,\"controlBehavior\":0,\"count\":0.0,\"durationInSec\":1,\"grade\":1,\"limitApp\":\"default\",\"maxQueueingTimeMs\":0,\"paramFlowItemList\":[],\"resource\":\"rs\"}}" .equals(JSON.toJSONString(paramFlowRule))); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersionTest.java ================================================ package com.alibaba.csp.sentinel.dashboard.datasource.entity; import static org.junit.Assert.*; import org.junit.Test; public class SentinelVersionTest { @Test public void testEqual() { assertEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 0, 0)); assertNotEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 2, 3)); assertNotEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 0, 0, "")); assertEquals(new SentinelVersion(1, 0, 0, ""), new SentinelVersion(1, 0, 0, "")); assertNotEquals(new SentinelVersion(1, 0, 0, ""), new SentinelVersion(1, 0, 0, null)); assertEquals(new SentinelVersion(1, 0, 0, null), new SentinelVersion(1, 0, 0, null)); } @Test public void testGreater() { assertTrue(new SentinelVersion(2, 0, 0).greaterThan(new SentinelVersion(1, 0, 0))); assertTrue(new SentinelVersion(1, 1, 0).greaterThan(new SentinelVersion(1, 0, 0))); assertTrue(new SentinelVersion(1, 1, 2).greaterThan(new SentinelVersion(1, 1, 0))); assertTrue(new SentinelVersion(1, 1, 4).greaterThan(new SentinelVersion(1, 1, 3))); assertFalse(new SentinelVersion(1, 0, 0).greaterThan(new SentinelVersion(1, 0, 0))); assertFalse(new SentinelVersion(1, 0, 0).greaterThan(new SentinelVersion(1, 1, 0))); assertFalse(new SentinelVersion(1, 1, 3).greaterThan(new SentinelVersion(1, 1, 3))); assertFalse(new SentinelVersion(1, 1, 2).greaterThan(new SentinelVersion(1, 1, 3))); assertFalse(new SentinelVersion(1, 0, 0, "").greaterThan(new SentinelVersion(1, 0, 0))); assertTrue(new SentinelVersion(1, 0, 1).greaterThan(new SentinelVersion(1, 0, 0))); assertTrue(new SentinelVersion(1, 0, 1, "a").greaterThan(new SentinelVersion(1, 0, 0, "b"))); assertFalse(new SentinelVersion(1, 0, 0, "b").greaterThan(new SentinelVersion(1, 0, 0, "a"))); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/discovery/AppInfoTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import java.util.ConcurrentModificationException; import java.util.Set; import org.junit.Test; import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; import static org.junit.Assert.*; public class AppInfoTest { @Test public void testConcurrentGetMachines() throws Exception { AppInfo appInfo = new AppInfo("testApp"); appInfo.addMachine(genMachineInfo("hostName1", "10.18.129.91")); appInfo.addMachine(genMachineInfo("hostName2", "10.18.129.92")); Set machines = appInfo.getMachines(); new Thread(() -> { try { for (MachineInfo m : machines) { System.out.println(m); try { Thread.sleep(200); } catch (InterruptedException e) { } } } catch (ConcurrentModificationException e) { e.printStackTrace(); fail(); } }).start(); Thread.sleep(100); try { appInfo.addMachine(genMachineInfo("hostName3", "10.18.129.93")); } catch (ConcurrentModificationException e) { e.printStackTrace(); fail(); } Thread.sleep(1000); } private MachineInfo genMachineInfo(String hostName, String ip) { MachineInfo machine = new MachineInfo(); machine.setApp("testApp"); machine.setHostname(hostName); machine.setIp(ip); machine.setPort(8719); machine.setVersion(String.valueOf(System.currentTimeMillis())); return machine; } @Test public void addRemoveMachineTest() { AppInfo appInfo = new AppInfo("default"); assertEquals("default", appInfo.getApp()); assertEquals(0, appInfo.getMachines().size()); //add one { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp("default"); machineInfo.setHostname("bogon"); machineInfo.setIp("127.0.0.1"); machineInfo.setPort(3389); machineInfo.setLastHeartbeat(System.currentTimeMillis()); machineInfo.setHeartbeatVersion(1); machineInfo.setVersion("0.4.1"); appInfo.addMachine(machineInfo); } assertEquals(1, appInfo.getMachines().size()); //add duplicated one { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp("default"); machineInfo.setHostname("bogon"); machineInfo.setIp("127.0.0.1"); machineInfo.setPort(3389); machineInfo.setLastHeartbeat(System.currentTimeMillis()); machineInfo.setHeartbeatVersion(1); machineInfo.setVersion("0.4.2"); appInfo.addMachine(machineInfo); } assertEquals(1, appInfo.getMachines().size()); //add different one { MachineInfo machineInfo = new MachineInfo(); machineInfo.setApp("default"); machineInfo.setHostname("bogon"); machineInfo.setIp("127.0.0.1"); machineInfo.setPort(3390); machineInfo.setLastHeartbeat(System.currentTimeMillis()); machineInfo.setHeartbeatVersion(1); machineInfo.setVersion("0.4.3"); appInfo.addMachine(machineInfo); } assertEquals(2, appInfo.getMachines().size()); appInfo.removeMachine("127.0.0.1", 3389); assertEquals(1, appInfo.getMachines().size()); appInfo.removeMachine("127.0.0.1", 3390); assertEquals(0, appInfo.getMachines().size()); } @Test public void testHealthyAndDead() { System.setProperty(DashboardConfig.CONFIG_HIDE_APP_NO_MACHINE_MILLIS, "60000"); System.setProperty(DashboardConfig.CONFIG_REMOVE_APP_NO_MACHINE_MILLIS, "600000"); DashboardConfig.clearCache(); String appName = "default"; AppInfo appInfo = new AppInfo(); appInfo.setApp(appName); { MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801); machineInfo.setHeartbeatVersion(1); machineInfo.setLastHeartbeat(System.currentTimeMillis()); appInfo.addMachine(machineInfo); } assertTrue(appInfo.isShown()); assertFalse(appInfo.isDead()); { MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801); machineInfo.setHeartbeatVersion(1); machineInfo.setLastHeartbeat(System.currentTimeMillis() - 70000); appInfo.addMachine(machineInfo); } assertFalse(appInfo.isShown()); assertFalse(appInfo.isDead()); { MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801); machineInfo.setHeartbeatVersion(1); machineInfo.setLastHeartbeat(System.currentTimeMillis() - 700000); appInfo.addMachine(machineInfo); } assertFalse(appInfo.isShown()); assertTrue(appInfo.isDead()); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/discovery/MachineInfoTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.discovery; import static org.junit.Assert.*; import org.junit.Test; import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; /** * @author Jason Joo */ public class MachineInfoTest { @Test public void testHealthyAndDead() { System.setProperty(DashboardConfig.CONFIG_UNHEALTHY_MACHINE_MILLIS, "60000"); System.setProperty(DashboardConfig.CONFIG_AUTO_REMOVE_MACHINE_MILLIS, "600000"); DashboardConfig.clearCache(); MachineInfo machineInfo = new MachineInfo(); machineInfo.setHeartbeatVersion(1); machineInfo.setLastHeartbeat(System.currentTimeMillis() - 10000); assertTrue(machineInfo.isHealthy()); assertFalse(machineInfo.isDead()); machineInfo.setLastHeartbeat(System.currentTimeMillis() - 100000); assertFalse(machineInfo.isHealthy()); assertFalse(machineInfo.isDead()); machineInfo.setLastHeartbeat(System.currentTimeMillis() - 1000000); assertFalse(machineInfo.isHealthy()); assertTrue(machineInfo.isDead()); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepositoryTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.repository.metric; import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.junit.Assert.*; /** * Test cases for {@link InMemoryMetricsRepository}. * * @author Nick Tan */ public class InMemoryMetricsRepositoryTest { private final static String DEFAULT_APP = "defaultApp"; private final static String DEFAULT_RESOURCE = "defaultResource"; private static final long EXPIRE_TIME = 1000 * 60 * 5L; private InMemoryMetricsRepository inMemoryMetricsRepository; private ExecutorService executorService; @Before public void setUp() throws Exception { inMemoryMetricsRepository = new InMemoryMetricsRepository(); executorService = Executors.newFixedThreadPool(8); } @After public void tearDown() { executorService.shutdownNow(); } @Test public void testSave() { MetricEntity entry = new MetricEntity(); entry.setApp("testSave"); entry.setResource("testResource"); entry.setTimestamp(new Date(System.currentTimeMillis())); entry.setPassQps(1L); entry.setExceptionQps(1L); entry.setBlockQps(0L); entry.setSuccessQps(1L); inMemoryMetricsRepository.save(entry); List resources = inMemoryMetricsRepository.listResourcesOfApp("testSave"); Assert.assertTrue(resources.size() == 1 && "testResource".equals(resources.get(0))); } @Test public void testSaveAll() { List entities = new ArrayList<>(10000); for (int i = 0; i < 10000; i++) { MetricEntity entry = new MetricEntity(); entry.setApp("testSaveAll"); entry.setResource("testResource" + i); entry.setTimestamp(new Date(System.currentTimeMillis())); entry.setPassQps(1L); entry.setExceptionQps(1L); entry.setBlockQps(0L); entry.setSuccessQps(1L); entities.add(entry); } inMemoryMetricsRepository.saveAll(entities); List result = inMemoryMetricsRepository.listResourcesOfApp("testSaveAll"); Assert.assertTrue(result.size() == entities.size()); } @Test public void testExpireMetric() { long now = System.currentTimeMillis(); MetricEntity expireEntry = new MetricEntity(); expireEntry.setApp(DEFAULT_APP); expireEntry.setResource(DEFAULT_RESOURCE); expireEntry.setTimestamp(new Date(now - EXPIRE_TIME - 1L)); expireEntry.setPassQps(1L); expireEntry.setExceptionQps(1L); expireEntry.setBlockQps(0L); expireEntry.setSuccessQps(1L); inMemoryMetricsRepository.save(expireEntry); MetricEntity entry = new MetricEntity(); entry.setApp(DEFAULT_APP); entry.setResource(DEFAULT_RESOURCE); entry.setTimestamp(new Date(now)); entry.setPassQps(1L); entry.setExceptionQps(1L); entry.setBlockQps(0L); entry.setSuccessQps(1L); inMemoryMetricsRepository.save(entry); List list = inMemoryMetricsRepository.queryByAppAndResourceBetween( DEFAULT_APP, DEFAULT_RESOURCE, now - EXPIRE_TIME, now); assertFalse(CollectionUtils.isEmpty(list)); assertEquals(1, list.size()); assertTrue(list.get(0).getTimestamp().getTime() >= now - EXPIRE_TIME && list.get(0).getTimestamp().getTime() <= now); } @Test public void testConcurrentPutAndGet() { List futures = new ArrayList<>(10000); final CyclicBarrier cyclicBarrier = new CyclicBarrier(8); for (int j = 0; j < 10000; j++) { final int finalJ = j; futures.add(CompletableFuture.runAsync(() -> { try { cyclicBarrier.await(); if (finalJ % 2 == 0) { batchSave(); } else { inMemoryMetricsRepository.listResourcesOfApp(DEFAULT_APP); } } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }, executorService) ); } CompletableFuture all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); try { all.get(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.getCause().printStackTrace(); if (e.getCause() instanceof ConcurrentModificationException) { fail("concurrent error occurred"); } else { fail("unexpected exception"); } } catch (TimeoutException e) { fail("allOf future timeout"); } } private void batchSave() { for (int i = 0; i < 100; i++) { MetricEntity entry = new MetricEntity(); entry.setApp(DEFAULT_APP); entry.setResource(DEFAULT_RESOURCE); entry.setTimestamp(new Date(System.currentTimeMillis())); entry.setPassQps(1L); entry.setExceptionQps(1L); entry.setBlockQps(0L); entry.setSuccessQps(1L); inMemoryMetricsRepository.save(entry); } } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/apollo/ApolloConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.apollo; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import com.ctrip.framework.apollo.openapi.client.ApolloOpenApiClient; /** * @author hantianwei@gmail.com * @since 1.5.0 */ @Configuration public class ApolloConfig { @Bean public Converter, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ApolloOpenApiClient apolloOpenApiClient() { ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder() .withPortalUrl("http://localhost:10034") .withToken("token") .build(); return client; } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/apollo/ApolloConfigUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.apollo; /** * @author hantianwei@gmail.com * @since 1.5.0 */ public final class ApolloConfigUtil { public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; private ApolloConfigUtil() { } public static String getFlowDataId(String appName) { return String.format("%s%s", appName, FLOW_DATA_ID_POSTFIX); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/apollo/FlowRuleApolloProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.apollo; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.StringUtil; import com.ctrip.framework.apollo.openapi.client.ApolloOpenApiClient; import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO; import com.ctrip.framework.apollo.openapi.dto.OpenNamespaceDTO; /** * @author hantianwei@gmail.com * @since 1.5.0 */ @Component("flowRuleApolloProvider") public class FlowRuleApolloProvider implements DynamicRuleProvider> { @Autowired private ApolloOpenApiClient apolloOpenApiClient; @Autowired private Converter> converter; @Override public List getRules(String appName) throws Exception { String appId = "appId"; String flowDataId = ApolloConfigUtil.getFlowDataId(appName); OpenNamespaceDTO openNamespaceDTO = apolloOpenApiClient.getNamespace(appId, "DEV", "default", "application"); String rules = openNamespaceDTO .getItems() .stream() .filter(p -> p.getKey().equals(flowDataId)) .map(OpenItemDTO::getValue) .findFirst() .orElse(""); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/apollo/FlowRuleApolloPublisher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.apollo; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import com.ctrip.framework.apollo.openapi.client.ApolloOpenApiClient; import com.ctrip.framework.apollo.openapi.dto.NamespaceReleaseDTO; import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO; /** * @author hantianwei@gmail.com * @since 1.5.0 */ @Component("flowRuleApolloPublisher") public class FlowRuleApolloPublisher implements DynamicRulePublisher> { @Autowired private ApolloOpenApiClient apolloOpenApiClient; @Autowired private Converter, String> converter; @Override public void publish(String app, List rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } // Increase the configuration String appId = "appId"; String flowDataId = ApolloConfigUtil.getFlowDataId(app); OpenItemDTO openItemDTO = new OpenItemDTO(); openItemDTO.setKey(flowDataId); openItemDTO.setValue(converter.convert(rules)); openItemDTO.setComment("Program auto-join"); openItemDTO.setDataChangeCreatedBy("some-operator"); apolloOpenApiClient.createOrUpdateItem(appId, "DEV", "default", "application", openItemDTO); // Release configuration NamespaceReleaseDTO namespaceReleaseDTO = new NamespaceReleaseDTO(); namespaceReleaseDTO.setEmergencyPublish(true); namespaceReleaseDTO.setReleaseComment("Modify or add configurations"); namespaceReleaseDTO.setReleasedBy("some-operator"); namespaceReleaseDTO.setReleaseTitle("Modify or add configurations"); apolloOpenApiClient.publishNamespace(appId, "DEV", "default", "application", namespaceReleaseDTO); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author Eric Zhao * @since 1.4.0 */ @Component("flowRuleNacosProvider") public class FlowRuleNacosProvider implements DynamicRuleProvider> { @Autowired private ConfigService configService; @Autowired private Converter> converter; @Override public List getRules(String appName) throws Exception { String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, 3000); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import java.util.List; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author Eric Zhao * @since 1.4.0 */ @Component("flowRuleNacosPublisher") public class FlowRuleNacosPublisher implements DynamicRulePublisher> { @Autowired private ConfigService configService; @Autowired private Converter, String> converter; @Override public void publish(String app, List rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, converter.convert(rules)); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/NacosConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import java.util.List; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao * @since 1.4.0 */ @Configuration public class NacosConfig { @Bean public Converter, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { return ConfigFactory.createConfigService("localhost"); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; /** * @author Eric Zhao * @since 1.4.0 */ public final class NacosConfigUtil { public static final String GROUP_ID = "SENTINEL_GROUP"; public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules"; public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map"; /** * cc for `cluster-client` */ public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config"; /** * cs for `cluster-server` */ public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config"; public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config"; public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set"; private NacosConfigUtil() {} } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/zookeeper/FlowRuleZookeeperProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.zookeeper; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import org.apache.curator.framework.CuratorFramework; import org.apache.zookeeper.data.Stat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component("flowRuleZookeeperProvider") public class FlowRuleZookeeperProvider implements DynamicRuleProvider> { @Autowired private CuratorFramework zkClient; @Autowired private Converter> converter; @Override public List getRules(String appName) throws Exception { String zkPath = ZookeeperConfigUtil.getPath(appName); Stat stat = zkClient.checkExists().forPath(zkPath); if(stat == null){ return new ArrayList<>(0); } byte[] bytes = zkClient.getData().forPath(zkPath); if (null == bytes || bytes.length == 0) { return new ArrayList<>(); } String s = new String(bytes); return converter.convert(s); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/zookeeper/FlowRuleZookeeperPublisher.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.zookeeper; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import org.apache.curator.framework.CuratorFramework; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; @Component("flowRuleZookeeperPublisher") public class FlowRuleZookeeperPublisher implements DynamicRulePublisher> { @Autowired private CuratorFramework zkClient; @Autowired private Converter, String> converter; @Override public void publish(String app, List rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); String path = ZookeeperConfigUtil.getPath(app); Stat stat = zkClient.checkExists().forPath(path); if (stat == null) { zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, null); } byte[] data = CollectionUtils.isEmpty(rules) ? "[]".getBytes() : converter.convert(rules).getBytes(); zkClient.setData().forPath(path, data); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/zookeeper/ZookeeperConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.zookeeper; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class ZookeeperConfig { @Bean public Converter, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public CuratorFramework zkClient() { CuratorFramework zkClient = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(ZookeeperConfigUtil.SLEEP_TIME, ZookeeperConfigUtil.RETRY_TIMES)); zkClient.start(); return zkClient; } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/zookeeper/ZookeeperConfigUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.zookeeper; import org.apache.commons.lang.StringUtils; public class ZookeeperConfigUtil { public static final String RULE_ROOT_PATH = "/sentinel_rule_config"; public static final int RETRY_TIMES = 3; public static final int SLEEP_TIME = 1000; public static String getPath(String appName) { StringBuilder stringBuilder = new StringBuilder(RULE_ROOT_PATH); if (StringUtils.isBlank(appName)) { return stringBuilder.toString(); } if (appName.startsWith("/")) { stringBuilder.append(appName); } else { stringBuilder.append("/") .append(appName); } return stringBuilder.toString(); } } ================================================ FILE: sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtilsTest.java ================================================ package com.alibaba.csp.sentinel.dashboard.util; import static org.junit.Assert.*; import java.util.Optional; import org.junit.Test; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; public class VersionUtilsTest { @Test public void test() { Optional version = VersionUtils.parseVersion("1.2.3"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(3, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1.2"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(0, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1."); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(0, version.get().getMinorVersion()); assertEquals(0, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1.2."); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(0, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1.2.3."); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(3, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1.2.3.4"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(3, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(0, version.get().getMinorVersion()); assertEquals(0, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("1.2.3-"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(3, version.get().getFixVersion()); assertNull(version.get().getPostfix()); version = VersionUtils.parseVersion("-"); assertFalse(version.isPresent()); version = VersionUtils.parseVersion("-t"); assertFalse(version.isPresent()); version = VersionUtils.parseVersion(""); assertFalse(version.isPresent()); version = VersionUtils.parseVersion(null); assertFalse(version.isPresent()); version = VersionUtils.parseVersion("1.2.3-SNAPSHOTS"); assertTrue(version.isPresent()); assertEquals(1, version.get().getMajorVersion()); assertEquals(2, version.get().getMinorVersion()); assertEquals(3, version.get().getFixVersion()); assertEquals("SNAPSHOTS", version.get().getPostfix()); } } ================================================ FILE: sentinel-demo/README.md ================================================ # Sentinel Examples The examples demonstrate: - How to leverage basic features (e.g. flow control, circuit breaking, load protection) of Sentinel - How to use various data source extensions of Sentinel (e.g. file, Nacos, ZooKeeper) - How to use Dubbo with Sentinel - How to use Apache RocketMQ client with Sentinel - How to use Sentinel annotation support - How to add your own logic to Sentinel using Slot Chain SPI ================================================ FILE: sentinel-demo/pom.xml ================================================ 4.0.0 com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-demo pom sentinel-demo sentinel-demo-basic sentinel-demo-dynamic-file-rule sentinel-demo-rocketmq sentinel-demo-dubbo sentinel-demo-nacos-datasource sentinel-demo-zookeeper-datasource sentinel-demo-apollo-datasource sentinel-demo-annotation-spring-aop sentinel-demo-parameter-flow-control sentinel-demo-slot-spi sentinel-demo-slotchain-spi sentinel-demo-cluster sentinel-demo-command-handler sentinel-demo-spring-webflux sentinel-demo-apache-dubbo sentinel-demo-apache-httpclient sentinel-demo-sofa-rpc sentinel-demo-spring-cloud-gateway sentinel-demo-zuul-gateway sentinel-demo-etcd-datasource sentinel-demo-spring-webmvc sentinel-demo-zuul2-gateway sentinel-demo-log-logback sentinel-demo-okhttp sentinel-demo-jax-rs sentinel-demo-quarkus sentinel-demo-annotation-cdi-interceptor sentinel-demo-motan sentinel-demo-transport-spring-mvc sentinel-demo-servlet com.alibaba.csp sentinel-core org.apache.maven.plugins maven-deploy-plugin ${maven.deploy.version} true org.apache.maven.plugins maven-gpg-plugin true ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-annotation-cdi-interceptor 3.1.4.Final com.alibaba.csp sentinel-core com.alibaba.csp sentinel-annotation-cdi-interceptor com.alibaba.csp sentinel-transport-simple-http org.jboss.weld.se weld-se-shaded ${weld-se-shaded.version} ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/demo/annotation/cdi/interceptor/DemoApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.cdi.interceptor; import javax.enterprise.inject.se.SeContainer; import javax.enterprise.inject.se.SeContainerInitializer; /** * @author sea */ public class DemoApplication { public static void main(String[] args) { SeContainerInitializer containerInit = SeContainerInitializer.newInstance(); SeContainer container = containerInit.initialize(); TestService testService = container.select(TestService.class).get(); testService.test(); System.out.println(testService.hello(-1)); System.out.println(testService.hello(1)); System.out.println(testService.helloAnother("bad")); try { System.out.println(testService.helloAnother("foo")); } catch (IllegalStateException e) { System.err.println(e.getMessage()); } System.out.println(testService.helloAnother("weld")); container.close(); System.exit(0); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/demo/annotation/cdi/interceptor/ExceptionUtil.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Eric Zhao */ public final class ExceptionUtil { public static void handleException(BlockException ex) { // Handler method that handles BlockException when blocked. // The method parameter list should match original method, with the last additional // parameter with type BlockException. The return type should be same as the original method. // The block handler method should be located in the same class with original method by default. // If you want to use method in other classes, you can set the blockHandlerClass // with corresponding Class (Note the method in other classes must be static). System.out.println("Oops: " + ex.getClass().getCanonicalName()); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/demo/annotation/cdi/interceptor/TestService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.cdi.interceptor; /** * @author Eric Zhao */ public interface TestService { void test(); String hello(long s); String helloAnother(String name); } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/demo/annotation/cdi/interceptor/TestServiceImpl.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding; import javax.enterprise.context.ApplicationScoped; /** * @author Eric Zhao */ @ApplicationScoped public class TestServiceImpl implements TestService { @Override @SentinelResourceBinding(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class}) public void test() { System.out.println("Test"); } @Override @SentinelResourceBinding(value = "hello", fallback = "helloFallback") public String hello(long s) { if (s < 0) { throw new IllegalArgumentException("invalid arg"); } return String.format("Hello at %d", s); } @Override @SentinelResourceBinding(value = "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } public String helloFallback(long s, Throwable ex) { // Do some log here. return "Oops, error occurred at " + s + ", msg:" + ex.getMessage(); } public String defaultFallback() { System.out.println("Go to default fallback"); return "default_fallback"; } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml ================================================ com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-annotation-spring-aop 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-annotation-aspectj com.alibaba.csp sentinel-transport-simple-http org.springframework.boot spring-boot-starter-aop ${spring.boot.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/DemoApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Eric Zhao */ @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/config/AopConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop.config; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration public class AopConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/controller/DemoController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop.controller; import com.alibaba.csp.sentinel.demo.annotation.aop.service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao */ @RestController public class DemoController { @Autowired private TestService service; @GetMapping("/foo") public String apiFoo(@RequestParam(required = false) Long t) { if (t == null) { t = System.currentTimeMillis(); } service.test(); return service.hello(t); } @GetMapping("/bar") public String apiBar(@RequestParam(required = false) String t) { service.test(); return service.hello(t); } @GetMapping("/baz/{name}") public String apiBaz(@PathVariable("name") String name) { return service.helloAnother(name); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/ExceptionUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop.service; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Eric Zhao */ public final class ExceptionUtil { public static void handleException(BlockException ex) { // Handler method that handles BlockException when blocked. // The method parameter list should match original method, with the last additional // parameter with type BlockException. The return type should be same as the original method. // The block handler method should be located in the same class with original method by default. // If you want to use method in other classes, you can set the blockHandlerClass // with corresponding Class (Note the method in other classes must be static). System.out.println("Oops: " + ex.getClass().getCanonicalName()); } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop.service; /** * @author Eric Zhao */ public interface TestService { void test(); String hello(long s); String hello(String s); String helloAnother(String name); } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.annotation.aop.service; import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.stereotype.Service; /** * @author Eric Zhao */ @Service public class TestServiceImpl implements TestService { @Override @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class}) public void test() { System.out.println("Test"); } @Override @SentinelResource(value = "hello", fallback = "helloFallback") public String hello(long s) { if (s < 0) { throw new IllegalArgumentException("invalid arg"); } return String.format("Hello at %d", s); } @Override @SentinelResource(value = "helloStr", fallback = "helloFallback") public String hello(String s) { if (s == null || s.trim().isEmpty()) { throw new IllegalArgumentException("unknown"); } return String.format("Hello, %s", s); } @Override @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } public String helloFallback(long s, Throwable ex) { // Do some log here. ex.printStackTrace(); return "Oops, error occurred at " + s; } private String helloFallback(String ignored, Throwable e) { // Do some log here. e.printStackTrace(); return "Hello, stranger"; } public String defaultFallback() { System.out.println("Go to default fallback"); return "default_fallback"; } } ================================================ FILE: sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/resources/application.properties ================================================ spring.application.name=sentinel-annotation-aspectj-example server.port=19966 ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/README.md ================================================ # Sentinel Apache Dubbo Demo This demo shows how to integrate Apache Dubbo **2.7.x or above version** with Sentinel using `sentinel-apache-dubbo-adapter` module. ## Run the demo For the provider demo `FooProviderBootstrap`, you need to add the following parameters when startup: ```shell -Djava.net.preferIPv4Stack=true -Dproject.name=dubbo-provider-demo ``` For the consumer demo `FooConsumerBootstrap`, you need to add the following parameters when startup: ```shell -Djava.net.preferIPv4Stack=true -Dproject.name=dubbo-consumer-demo ``` ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-apache-dubbo org.apache.dubbo dubbo 2.7.18 io.netty netty-all 4.1.75.Final org.springframework spring-context-support 5.3.15 com.alibaba.csp sentinel-apache-dubbo-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ${project.version} ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.ConsumerConfiguration; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.FooServiceConsumer; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.apache.dubbo.rpc.AsyncRpcResult; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.util.ArrayList; import java.util.List; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8721
           * -Dproject.name=dubbo-consumer-demo
           * 
          * * @author Eric Zhao */ public class FooConsumerBootstrap { private static final String INTERFACE_RES_KEY = FooService.class.getName(); private static final String RES_KEY = INTERFACE_RES_KEY + ":sayHello(java.lang.String)"; public static void main(String[] args) throws InterruptedException { AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext(); consumerContext.register(ConsumerConfiguration.class); consumerContext.refresh(); initFlowRule(10, false); FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class); for (int i = 0; i < 15; i++) { try { String message = service.sayHello("Eric"); System.out.println("Success: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } // method flowcontrol Thread.sleep(1000); initFlowRule(20, true); for (int i = 0; i < 10; i++) { try { String message = service.sayHello("Eric"); System.out.println("Success: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); System.out.println("fallback:" + service.doAnother()); } catch (Exception ex) { ex.printStackTrace(); } } // fallback to result Thread.sleep(1000); registryCustomFallback(); for (int i = 0; i < 10; i++) { try { String message = service.sayHello("Eric"); System.out.println("Result: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } // fallback to exception Thread.sleep(1000); registryCustomFallbackForCustomException(); for (int i = 0; i < 10; i++) { try { String message = service.sayHello("Eric"); System.out.println("Result: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } Thread.sleep(1000); registryCustomFallbackWhenFallbackError(); for (int i = 0; i < 10; i++) { try { String message = service.sayHello("Eric"); System.out.println("Result: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } } public static void registryCustomFallback() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult("fallback", invocation)); } public static void registryCustomFallbackForCustomException() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult(new RuntimeException("fallback"), invocation)); } public static void registryCustomFallbackWhenFallbackError() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> { throw new RuntimeException("fallback"); }); } private static void initFlowRule(int interfaceFlowLimit, boolean method) { FlowRule flowRule = new FlowRule(INTERFACE_RES_KEY) .setCount(interfaceFlowLimit) .setGrade(RuleConstant.FLOW_GRADE_QPS); List list = new ArrayList<>(); if (method) { FlowRule flowRule1 = new FlowRule(RES_KEY) .setCount(5) .setGrade(RuleConstant.FLOW_GRADE_QPS); list.add(flowRule1); } list.add(flowRule); FlowRuleManager.loadRules(list); } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerExceptionDegradeBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo; import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.ConsumerConfiguration; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.FooServiceConsumer; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.RpcContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8721
           * -Dproject.name=dubbo-consumer-demo
           * 
          * * @author Zechao zheng */ public class FooConsumerExceptionDegradeBootstrap { private static final String INTERFACE_RES_KEY = FooService.class.getName(); private static final String RES_KEY = INTERFACE_RES_KEY + ":sayHello(java.lang.String)"; public static void main(String[] args) throws InterruptedException, ExecutionException { AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext(); consumerContext.register(ConsumerConfiguration.class); consumerContext.refresh(); FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class); initExceptionFallback(3); registryCustomFallback(); for (int i = 0; i < 10; i++) { try { String message = service.exceptionTest(true, false); System.out.println("Result: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } // sleep 3s to skip the time window initExceptionFallback(3); Thread.sleep(3000); for (int i = 0; i < 10; i++) { try { String message = service.exceptionTest(false, true); System.out.println("Result: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } initExceptionFallback(3); Thread.sleep(3000); try { // timeout to trigger the fallback CompletableFuture completableFuture = RpcContext.getContext().asyncCall(() -> service.exceptionTest(false, true)); System.out.println("Result: " + completableFuture.get()); } catch (Exception e) { e.printStackTrace(); } for (int i = 0; i < 10; i++) { try { CompletableFuture result = RpcContext.getContext().asyncCall(() -> service.exceptionTest(false, true)); System.out.println("Result: " + result.get()); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } } public static void registryCustomFallback() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult("fallback", invocation)); } public static void initExceptionFallback(int timewindow) { DegradeRule degradeRule = new DegradeRule(INTERFACE_RES_KEY) .setCount(0.5) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(timewindow) .setMinRequestAmount(1); DegradeRuleManager.loadRules(Collections.singletonList(degradeRule)); } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooProviderBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo; import java.util.Collections; import com.alibaba.csp.sentinel.demo.apache.dubbo.provider.ProviderConfiguration; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Provider demo for Apache Dubbo 2.7.x or above. Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8720
           * -Dproject.name=dubbo-provider-demo
           * 
          * * @author Eric Zhao */ public class FooProviderBootstrap { public static void main(String[] args) { // Users don't need to manually call this method. // Only for eager initialization. InitExecutor.doInit(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(ProviderConfiguration.class); context.refresh(); System.out.println("Service provider is ready"); } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo; /** * @author Eric Zhao */ public interface FooService { String sayHello(String name); String doAnother(); String exceptionTest(boolean biz, boolean timeout); } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/ConsumerConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo.consumer; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ConsumerConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration @DubboComponentScan public class ConsumerConfiguration { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("demo-consumer"); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("multicast://224.5.6.7:1234"); return registryConfig; } @Bean public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); // Uncomment below line if you don't want to enable Sentinel for Dubbo service consumers. // consumerConfig.setFilter("-sentinel.dubbo.consumer.filter"); return consumerConfig; } @Bean public FooServiceConsumer annotationDemoServiceConsumer() { return new FooServiceConsumer(); } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/FooServiceConsumer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo.consumer; import com.alibaba.csp.sentinel.demo.apache.dubbo.FooService; import org.apache.dubbo.config.annotation.Reference; /** * @author Eric Zhao */ public class FooServiceConsumer { @Reference(url = "dubbo://127.0.0.1:25758", timeout = 500) private FooService fooService; public String sayHello(String name) { return fooService.sayHello(name); } public String doAnother() { return fooService.doAnother(); } public String exceptionTest(boolean biz, boolean timeout) { return fooService.exceptionTest(biz, timeout); } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/FooServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo.provider; import com.alibaba.csp.sentinel.demo.apache.dubbo.FooService; import org.apache.dubbo.config.annotation.Service; import java.time.LocalDateTime; /** * @author Eric Zhao */ @Service public class FooServiceImpl implements FooService { @Override public String sayHello(String name) { return String.format("Hello, %s at %s", name, LocalDateTime.now()); } @Override public String doAnother() { return LocalDateTime.now().toString(); } @Override public String exceptionTest(boolean biz, boolean timeout) { if (biz) { throw new RuntimeException("biz exception"); } if (timeout) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } return "Success"; } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/ProviderConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.dubbo.provider; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration @DubboComponentScan public class ProviderConfiguration { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("demo-provider"); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("multicast://224.5.6.7:1234"); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(25758); return protocolConfig; } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-httpclient/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-apache-httpclient 2.5.12 4.3 4.5.13 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-apache-httpclient-adapter ${project.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-test ${spring.boot.version} org.apache.httpcomponents httpclient ${apache.httpclient.version} ================================================ FILE: sentinel-demo/sentinel-demo-apache-httpclient/src/main/java/com/alibaba/csp/sentinel/demo/apache/httpclient/ApacheHttpClientDemoApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.httpclient; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author zhaoyuguang */ @SpringBootApplication public class ApacheHttpClientDemoApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(ApacheHttpClientDemoApplication.class); } @Override public void run(String... args) { } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-httpclient/src/main/java/com/alibaba/csp/sentinel/demo/apache/httpclient/controller/ApacheHttpClientTestController.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.apache.httpclient.controller; import com.alibaba.csp.sentinel.adapter.apache.httpclient.SentinelApacheHttpClientBuilder; import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig; import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /** * @author zhaoyuguang */ @RestController public class ApacheHttpClientTestController { @Value("${server.port}") private Integer port; @RequestMapping("/httpclient/back") public String back() { System.out.println("back"); return "Welcome Back!"; } @RequestMapping("/httpclient/back/{id}") public String back(@PathVariable String id) { System.out.println("back"); return "Welcome Back! " + id; } @RequestMapping("/httpclient/sync") public String sync() throws Exception { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setExtractor(new ApacheHttpClientResourceExtractor() { @Override public String extractor(HttpRequestWrapper request) { String contains = "/httpclient/back/"; String uri = request.getRequestLine().getUri(); if (uri.startsWith(contains)) { uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; } return request.getMethod() + ":" + uri; } }); CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back"); return getRemoteString(httpclient, httpGet); } @RequestMapping("/httpclient/sync/{id}") public String sync(@PathVariable String id) throws Exception { SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); config.setExtractor(new ApacheHttpClientResourceExtractor() { @Override public String extractor(HttpRequestWrapper request) { String contains = "/httpclient/back/"; String uri = request.getRequestLine().getUri(); if (uri.startsWith(contains)) { uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; } return request.getMethod() + ":" + uri; } }); CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back/" + id); return getRemoteString(httpclient, httpGet); } private String getRemoteString(CloseableHttpClient httpclient, HttpGet httpGet) throws IOException { String result; HttpContext context = new BasicHttpContext(); CloseableHttpResponse response; response = httpclient.execute(httpGet, context); try { HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "utf-8"); EntityUtils.consume(entity); } finally { response.close(); } httpclient.close(); return result; } } ================================================ FILE: sentinel-demo/sentinel-demo-apache-httpclient/src/main/resources/application.properties ================================================ spring.application.name=sentinel-demo-apache-httpclient server.port=8083 ================================================ FILE: sentinel-demo/sentinel-demo-apollo-datasource/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-apollo-datasource 2.17.1 1.7.25 org.slf4j slf4j-api ${slf4j.version} org.apache.logging.log4j log4j-core ${log4j2.version} org.apache.logging.log4j log4j-api ${log4j2.version} org.apache.logging.log4j log4j-slf4j-impl ${log4j2.version} com.alibaba.csp sentinel-datasource-apollo com.alibaba fastjson org.apache.logging.log4j log4j-slf4j-impl ================================================ FILE: sentinel-demo/sentinel-demo-apollo-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/apollo/ApolloDataSourceDemo.java ================================================ package com.alibaba.csp.sentinel.demo.datasource.apollo; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import java.util.List; /** * This demo shows how to use Apollo as the data source of Sentinel rules. *
          * You need to first set up data as follows: *
            *
          1. Create an application with app id as sentinel-demo in Apollo
          2. *
          3. * Create a configuration with key as flowRules and value as follows: *
             *      [
                      {
                        "resource": "TestResource",
                        "controlBehavior": 0,
                        "count": 5.0,
                        "grade": 1,
                        "limitApp": "default",
                        "strategy": 0
                      }
                    ]
             *    
            *
          4. *
          5. Publish the application namespace
          6. *
          * Then you could start this demo and adjust the rule configuration as you wish. * The rule changes will take effect in real time. * * @author Jason Song */ public class ApolloDataSourceDemo { private static final String KEY = "TestResource"; public static void main(String[] args) { loadRules(); // Assume we config: resource is `TestResource`, initial QPS threshold is 5. FlowQpsRunner runner = new FlowQpsRunner(KEY, 1, 100); runner.simulateTraffic(); runner.tick(); } private static void loadRules() { // Set up basic information, only for demo purpose. You may adjust them based on your actual environment. // For more information, please refer https://github.com/ctripcorp/apollo String appId = "sentinel-demo"; String apolloMetaServerAddress = "http://localhost:8080"; System.setProperty("app.id", appId); System.setProperty("apollo.meta", apolloMetaServerAddress); String namespaceName = "application"; String flowRuleKey = "flowRules"; // It's better to provide a meaningful default value. String defaultFlowRules = "[]"; ReadableDataSource> flowRuleDataSource = new ApolloDataSource<>(namespaceName, flowRuleKey, defaultFlowRules, source -> JSON.parseObject(source, new TypeReference>() { })); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } } ================================================ FILE: sentinel-demo/sentinel-demo-apollo-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/apollo/FlowQpsRunner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.apollo; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Flow QPS runner. * * @author Carpenter Lee * @author Eric Zhao */ class FlowQpsRunner { private final String resourceName; private final int threadCount; private int seconds; public FlowQpsRunner(String resourceName, int threadCount, int seconds) { this.resourceName = resourceName; this.threadCount = threadCount; this.seconds = seconds; } private final AtomicInteger pass = new AtomicInteger(); private final AtomicInteger block = new AtomicInteger(); private final AtomicInteger total = new AtomicInteger(); private volatile boolean stop = false; public void simulateTraffic() { for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("simulate-traffic-Task"); t.start(); } } public void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } final class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(resourceName); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } final class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + " send qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-apollo-datasource/src/main/resources/log4j2.xml ================================================ ================================================ FILE: sentinel-demo/sentinel-demo-basic/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-demo ${revision} ../pom.xml sentinel-demo-basic ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/AsyncEntryDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import com.alibaba.csp.sentinel.AsyncEntry; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; /** * An example for asynchronous entry in Sentinel. * * @author Eric Zhao * @since 0.2.0 */ public class AsyncEntryDemo { private void invoke(String arg, Consumer handler) { CompletableFuture.runAsync(() -> { try { TimeUnit.SECONDS.sleep(3); String resp = arg + ": " + System.currentTimeMillis(); handler.accept(resp); } catch (Exception ex) { ex.printStackTrace(); } }); } private void anotherAsync() { try { final AsyncEntry entry = SphU.asyncEntry("test-another-async"); CompletableFuture.runAsync(() -> { ContextUtil.runOnContext(entry.getAsyncContext(), () -> { try { TimeUnit.SECONDS.sleep(2); // Normal entry nested in asynchronous entry. anotherSyncInAsync(); System.out.println("Async result: 666"); } catch (InterruptedException e) { // Ignore. } finally { entry.exit(); } }); }); } catch (BlockException ex) { ex.printStackTrace(); } } private void fetchSync() { Entry entry = null; try { entry = SphU.entry("test-sync"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } private void fetchSyncInAsync() { Entry entry = null; try { entry = SphU.entry("test-sync-in-async"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } private void anotherSyncInAsync() { Entry entry = null; try { entry = SphU.entry("test-another-sync-in-async"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } private void directlyAsync() { try { final AsyncEntry entry = SphU.asyncEntry("test-async-not-nested"); this.invoke("abc", result -> { // If no nested entry later, we don't have to wrap in `ContextUtil.runOnContext()`. try { // Here to handle the async result (without other entry). } finally { // Exit the async entry. entry.exit(); } }); } catch (BlockException e) { // Request blocked, handle the exception. e.printStackTrace(); } } private void doAsyncThenSync() { try { // First we call an asynchronous resource. final AsyncEntry entry = SphU.asyncEntry("test-async"); this.invoke("abc", resp -> { // The thread is different from original caller thread for async entry. // So we need to wrap in the async context so that nested invocation entry // can be linked to the parent asynchronous entry. ContextUtil.runOnContext(entry.getAsyncContext(), () -> { try { // In the callback, we do another async invocation several times under the async context. for (int i = 0; i < 7; i++) { anotherAsync(); } System.out.println(resp); // Then we do a sync (normal) entry under current async context. fetchSyncInAsync(); } finally { // Exit the async entry. entry.exit(); } }); }); // Then we call a sync resource. fetchSync(); } catch (BlockException ex) { // Request blocked, handle the exception. ex.printStackTrace(); } } public static void main(String[] args) throws Exception { initFlowRule(); AsyncEntryDemo service = new AsyncEntryDemo(); // Expected invocation chain: // // EntranceNode: machine-root // -EntranceNode: async-context // --test-top // ---test-sync // ---test-async // ----test-another-async // -----test-another-sync-in-async // ----test-sync-in-async ContextUtil.enter("async-context", "originA"); Entry entry = null; try { entry = SphU.entry("test-top"); System.out.println("Do something..."); service.doAsyncThenSync(); } catch (BlockException ex) { // Request blocked, handle the exception. ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } TimeUnit.SECONDS.sleep(20); } private static void initFlowRule() { // Rule 1 won't take effect as the limitApp doesn't match. FlowRule rule1 = new FlowRule() .setResource("test-another-sync-in-async") .setLimitApp("originB") .as(FlowRule.class) .setCount(4) .setGrade(RuleConstant.FLOW_GRADE_QPS); // Rule 2 will take effect. FlowRule rule2 = new FlowRule() .setResource("test-another-async") .setLimitApp("default") .as(FlowRule.class) .setCount(5) .setGrade(RuleConstant.FLOW_GRADE_QPS); List ruleList = Arrays.asList(rule1, rule2); FlowRuleManager.loadRules(ruleList); } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/authority/AuthorityDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.authority; import java.util.Collections; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; /** * Authority rule is designed for limiting by request origins. In blacklist mode, * requests will be blocked when blacklist contains current origin, otherwise will pass. * In whitelist mode, only requests from whitelist origin can pass. * * @author Eric Zhao */ public class AuthorityDemo { private static final String RESOURCE_NAME = "testABC"; public static void main(String[] args) { System.out.println("========Testing for black list========"); initBlackRules(); testFor(RESOURCE_NAME, "appA"); testFor(RESOURCE_NAME, "appB"); testFor(RESOURCE_NAME, "appC"); testFor(RESOURCE_NAME, "appE"); System.out.println("========Testing for white list========"); initWhiteRules(); testFor(RESOURCE_NAME, "appA"); testFor(RESOURCE_NAME, "appB"); testFor(RESOURCE_NAME, "appC"); testFor(RESOURCE_NAME, "appE"); } private static void testFor(/*@NonNull*/ String resource, /*@NonNull*/ String origin) { ContextUtil.enter(resource, origin); Entry entry = null; try { entry = SphU.entry(resource); System.out.println(String.format("Passed for resource %s, origin is %s", resource, origin)); } catch (BlockException ex) { System.err.println(String.format("Blocked for resource %s, origin is %s", resource, origin)); } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } } private static void initWhiteRules() { AuthorityRule rule = new AuthorityRule(); rule.setResource(RESOURCE_NAME); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); rule.setLimitApp("appA,appE"); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); } private static void initBlackRules() { AuthorityRule rule = new AuthorityRule(); rule.setResource(RESOURCE_NAME); rule.setStrategy(RuleConstant.AUTHORITY_BLACK); rule.setLimitApp("appA,appB"); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/degrade/ExceptionRatioCircuitBreakerDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.degrade; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; import com.alibaba.csp.sentinel.util.TimeUtil; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @author jialiang.linjl * @author Eric Zhao */ public class ExceptionRatioCircuitBreakerDemo { private static final String KEY = "some_service"; private static AtomicInteger total = new AtomicInteger(); private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger bizException = new AtomicInteger(); private static volatile boolean stop = false; private static int seconds = 120; public static void main(String[] args) throws Exception { initDegradeRule(); registerStateChangeObserver(); startTick(); final int concurrency = 8; for (int i = 0; i < concurrency; i++) { Thread entryThread = new Thread(() -> { while (true) { Entry entry = null; try { entry = SphU.entry(KEY); sleep(ThreadLocalRandom.current().nextInt(5, 10)); pass.addAndGet(1); // Error probability is 45% if (ThreadLocalRandom.current().nextInt(0, 100) > 55) { // biz code raise an exception. throw new RuntimeException("oops"); } } catch (BlockException e) { block.addAndGet(1); sleep(ThreadLocalRandom.current().nextInt(5, 10)); } catch (Throwable t) { bizException.incrementAndGet(); // It's required to record exception here manually. Tracer.traceEntry(t, entry); } finally { total.addAndGet(1); if (entry != null) { entry.exit(); } } } }); entryThread.setName("sentinel-simulate-traffic-task-" + i); entryThread.start(); } } private static void registerStateChangeObserver() { EventObserverRegistry.getInstance().addStateChangeObserver("logging", (prevState, newState, rule, snapshotValue) -> { if (newState == State.OPEN) { System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(), TimeUtil.currentTimeMillis(), snapshotValue)); } else { System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(), TimeUtil.currentTimeMillis())); } }); } private static void initDegradeRule() { List rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(KEY) .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType()) // Set ratio threshold to 50%. .setCount(0.5d) .setStatIntervalMs(30000) .setMinRequestAmount(50) // Retry timeout (in second) .setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); System.out.println("Degrade rule loaded: " + rules); } private static void sleep(int timeMs) { try { TimeUnit.MILLISECONDS.sleep(timeMs); } catch (InterruptedException e) { // ignore } } private static void startTick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-tick-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("Begin to run! Go go go!"); System.out.println("See corresponding metrics.log for accurate statistic data"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; long oldBizException = 0; while (!stop) { sleep(1000); long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; long globalBizException = bizException.get(); long oneSecondBizException = globalBizException - oldBizException; oldBizException = globalBizException; System.out.println(TimeUtil.currentTimeMillis() + ", oneSecondTotal:" + oneSecondTotal + ", oneSecondPass:" + oneSecondPass + ", oneSecondBlock:" + oneSecondBlock + ", oneSecondBizException:" + oneSecondBizException); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total: " + total.get() + ", pass:" + pass.get() + ", block:" + block.get() + ", bizException:" + bizException.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/degrade/SlowRatioCircuitBreakerDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.degrade; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Run this demo, and the output will be like: * *
           * 1529399827825,total:0, pass:0, block:0
           * 1529399828825,total:4263, pass:100, block:4164
           * 1529399829825,total:19179, pass:4, block:19176 // circuit breaker opens
           * 1529399830824,total:19806, pass:0, block:19806
           * 1529399831825,total:19198, pass:0, block:19198
           * 1529399832824,total:19481, pass:0, block:19481
           * 1529399833826,total:19241, pass:0, block:19241
           * 1529399834826,total:17276, pass:0, block:17276
           * 1529399835826,total:18722, pass:0, block:18722
           * 1529399836826,total:19490, pass:0, block:19492
           * 1529399837828,total:19355, pass:0, block:19355
           * 1529399838827,total:11388, pass:0, block:11388
           * 1529399839829,total:14494, pass:104, block:14390 // After 10 seconds, the system restored
           * 1529399840854,total:18505, pass:0, block:18505
           * 1529399841854,total:19673, pass:0, block:19676
           * 
          * * @author jialiang.linjl * @author Eric Zhao */ public class SlowRatioCircuitBreakerDemo { private static final String KEY = "some_method"; private static volatile boolean stop = false; private static int seconds = 120; private static AtomicInteger total = new AtomicInteger(); private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); public static void main(String[] args) throws Exception { initDegradeRule(); registerStateChangeObserver(); startTick(); int concurrency = 8; for (int i = 0; i < concurrency; i++) { Thread entryThread = new Thread(() -> { while (true) { Entry entry = null; try { entry = SphU.entry(KEY); pass.incrementAndGet(); // RT: [40ms, 60ms) sleep(ThreadLocalRandom.current().nextInt(40, 60)); } catch (BlockException e) { block.incrementAndGet(); sleep(ThreadLocalRandom.current().nextInt(5, 10)); } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } } }); entryThread.setName("sentinel-simulate-traffic-task-" + i); entryThread.start(); } } private static void registerStateChangeObserver() { EventObserverRegistry.getInstance().addStateChangeObserver("logging", (prevState, newState, rule, snapshotValue) -> { if (newState == State.OPEN) { System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(), TimeUtil.currentTimeMillis(), snapshotValue)); } else { System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(), TimeUtil.currentTimeMillis())); } }); } private static void initDegradeRule() { List rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(KEY) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) // Max allowed response time .setCount(50) // Retry timeout (in second) .setTimeWindow(10) // Circuit breaker opens when slow request ratio > 60% .setSlowRatioThreshold(0.6) .setMinRequestAmount(100) .setStatIntervalMs(20000); rules.add(rule); DegradeRuleManager.loadRules(rules); System.out.println("Degrade rule loaded: " + rules); } private static void sleep(int timeMs) { try { TimeUnit.MILLISECONDS.sleep(timeMs); } catch (InterruptedException e) { // ignore } } private static void startTick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-tick-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("Begin to run! Go go go!"); System.out.println("See corresponding metrics.log for accurate statistic data"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { sleep(1000); long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total: " + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowQpsDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; /** * @author jialiang.linjl */ public class FlowQpsDemo { private static final String KEY = "abc"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 32; private static int seconds = 60 + 40; public static void main(String[] args) throws Exception { initFlowQpsRule(); tick(); // first make the system run on a very low condition simulateTraffic(); System.out.println("===== begin to do flow control"); System.out.println("only 20 requests per second can pass"); } private static void initFlowQpsRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); // set limit qps to 20 rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void simulateTraffic() { for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("simulate-traffic-Task"); t.start(); } } private static void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + " send qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } static class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowQpsRegexDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.util.TimeUtil; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class FlowQpsRegexDemo { private static final String KEY = "/A/.*"; private static Map passMap = new ConcurrentHashMap<>(); private static Map blockMap = new ConcurrentHashMap<>(); private static Map totalMap = new ConcurrentHashMap<>(); private static final List resourceNameList = Arrays.asList("/A/a", "/A/c", "/B/a"); private static volatile boolean stop = false; private static final int threadCount = 10; private static int seconds = 60 + 40; public static void main(String[] args) throws Exception { initFlowQpsRule(); tick(); // first make the system run on a very low condition simulateTraffic(); System.out.println("===== begin to do flow control"); System.out.println("Resources prefixed with /A/ can only pass 20 requests per second"); } private static void initFlowQpsRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); // set limit qps to 20 rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setRegex(true); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void simulateTraffic() { for (String resourceName : resourceNameList) { passMap.put(resourceName, new AtomicInteger(0)); blockMap.put(resourceName, new AtomicInteger(0)); totalMap.put(resourceName, new AtomicInteger(0)); for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new FlowQpsRegexDemo.RunTask(resourceName)); t.setName("simulate-traffic-Task-" + resourceName); t.start(); } } } private static void tick() { Thread timer = new Thread(new FlowQpsRegexDemo.TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { private final Map oldTotalMap = new HashMap<>(); private final Map oldPassMap = new HashMap<>(); private final Map oldBlockMap = new HashMap<>(); TimerTask() { for (String resource : resourceNameList) { oldTotalMap.put(resource, 0L); oldPassMap.put(resource, 0L); oldBlockMap.put(resource, 0L); } } @Override public void run() { System.out.println("begin to statistic!!!"); while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } for (String resource : resourceNameList) { long oldTotal = oldTotalMap.get(resource); long oldPass = oldPassMap.get(resource); long oldBlock = oldBlockMap.get(resource); AtomicInteger pass = passMap.get(resource); AtomicInteger block = blockMap.get(resource); AtomicInteger total = totalMap.get(resource); long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotalMap.put(resource, globalTotal); long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPassMap.put(resource, globalPass); long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlockMap.put(resource, globalBlock); System.out.println(seconds + " " + resource + " send qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); } if (seconds-- <= 0) { stop = true; } } System.exit(0); } } static class RunTask implements Runnable { private final String resourceName; private final AtomicInteger pass; private final AtomicInteger block; private final AtomicInteger total; RunTask(String resourceName) { this.resourceName = resourceName; pass = passMap.get(resourceName); block = blockMap.get(resourceName); total = totalMap.get(resourceName); } @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(resourceName); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowThreadDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; /** * @author jialiang.linjl */ public class FlowThreadDemo { private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static AtomicInteger activeThread = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 60 + 40; private static volatile int methodBRunningTime = 2000; public static void main(String[] args) throws Exception { System.out.println( "MethodA will call methodB. After running for a while, methodB becomes fast, " + "which make methodA also become fast "); tick(); initFlowRule(); for (int i = 0; i < threadCount; i++) { Thread entryThread = new Thread(new Runnable() { @Override public void run() { while (true) { Entry methodA = null; try { TimeUnit.MILLISECONDS.sleep(5); methodA = SphU.entry("methodA"); activeThread.incrementAndGet(); Entry methodB = SphU.entry("methodB"); TimeUnit.MILLISECONDS.sleep(methodBRunningTime); methodB.exit(); pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (methodA != null) { methodA.exit(); activeThread.decrementAndGet(); } } } } }); entryThread.setName("working thread"); entryThread.start(); } } private static void initFlowRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource("methodA"); // set limit concurrent thread for 'methodA' to 20 rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + " total qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock + " activeThread:" + activeThread.get()); if (seconds-- <= 0) { stop = true; } if (seconds == 40) { System.out.println("method B is running much faster; more requests are allowed to pass"); methodBRunningTime = 20; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/PaceFlowDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; /** *

          * If {@link RuleConstant#CONTROL_BEHAVIOR_RATE_LIMITER} is set, incoming * requests are passing at regular interval. When a new request arrives, the * flow rule checks whether the interval between the new request and the * previous request. If the interval is less than the count set in the rule * first. If the interval is large, it will pass the request; otherwise, * sentinel will calculate the waiting time for this request. If the waiting * time is longer than the {@link FlowRule#maxQueueingTimeMs} set in the rule, * the request will be rejected immediately. * * This method is widely used for pulsed flow. When a large amount of flow * comes, we don't want to pass all these requests at once, which may drag the * system down. We can make the system handle these requests at a steady pace by * using this kind of rules. * *

          * This demo demonstrates how to use {@link RuleConstant#CONTROL_BEHAVIOR_RATE_LIMITER}. *

          * *

          * {@link #initPaceFlowRule() } create rules that uses * {@code CONTROL_BEHAVIOR_RATE_LIMITER}. *

          * {@link #simulatePulseFlow()} simulates 100 requests that arrives at almost the * same time. All these 100 request are passed at a fixed interval. * *

          * Run this demo, results are as follows: *

           * pace behavior
           * ....
           * 1528872403887 one request pass, cost 9348 ms // every 100 ms pass one request.
           * 1528872403986 one request pass, cost 9469 ms
           * 1528872404087 one request pass, cost 9570 ms
           * 1528872404187 one request pass, cost 9642 ms
           * 1528872404287 one request pass, cost 9770 ms
           * 1528872404387 one request pass, cost 9848 ms
           * 1528872404487 one request pass, cost 9970 ms
           * ...
           * done
           * total pass:100, total block:0
           * 
          * * Then we invoke {@link #initDefaultFlowRule()} to set rules with default behavior, and only 10 * requests will be allowed to pass, other requests will be rejected immediately. *

          * The output will be like: *

           * default behavior
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101279 one request pass, cost 0 ms
           * 1530500101280 one request pass, cost 1 ms
           * 1530500101280 one request pass, cost 0 ms
           * 1530500101280 one request pass, cost 0 ms
           * 1530500101280 one request pass, cost 0 ms
           * done
           * total pass:10, total block:90 // 10 requests passed, other 90 requests rejected immediately.
           * 
          * * @author jialiang.linjl */ public class PaceFlowDemo { private static final String KEY = "abc"; private static volatile CountDownLatch countDown; private static final Integer requestQps = 100; private static final Integer count = 10; private static final AtomicInteger done = new AtomicInteger(); private static final AtomicInteger pass = new AtomicInteger(); private static final AtomicInteger block = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { System.out.println("pace behavior"); countDown = new CountDownLatch(1); initPaceFlowRule(); simulatePulseFlow(); countDown.await(); System.out.println("done"); System.out.println("total pass:" + pass.get() + ", total block:" + block.get()); System.out.println(); System.out.println("default behavior"); TimeUnit.SECONDS.sleep(5); done.set(0); pass.set(0); block.set(0); countDown = new CountDownLatch(1); initDefaultFlowRule(); simulatePulseFlow(); countDown.await(); System.out.println("done"); System.out.println("total pass:" + pass.get() + ", total block:" + block.get()); System.exit(0); } private static void initPaceFlowRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); rule1.setCount(count); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); /* * CONTROL_BEHAVIOR_RATE_LIMITER means requests more than threshold will be queueing in the queue, * until the queueing time is more than {@link FlowRule#maxQueueingTimeMs}, the requests will be rejected. */ rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); rule1.setMaxQueueingTimeMs(20 * 1000); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void initDefaultFlowRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); rule1.setCount(count); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); // CONTROL_BEHAVIOR_DEFAULT means requests more than threshold will be rejected immediately. rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rules.add(rule1); FlowRuleManager.loadRules(rules); } private static void simulatePulseFlow() { for (int i = 0; i < requestQps; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { long startTime = TimeUtil.currentTimeMillis(); Entry entry = null; try { entry = SphU.entry(KEY); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { if (entry != null) { entry.exit(); pass.incrementAndGet(); long cost = TimeUtil.currentTimeMillis() - startTime; System.out.println( TimeUtil.currentTimeMillis() + " one request pass, cost " + cost + " ms"); } } try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e1) { // ignore } if (done.incrementAndGet() >= requestQps) { countDown.countDown(); } } }, "Thread " + i); thread.start(); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/WarmUpFlowDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; /** * When {@link FlowRule#controlBehavior} set to {@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP}, real passed qps will * gradually increase to {@link FlowRule#count}, other than burst increasing. *

          * Run this demo, results are as follows: *

           * ...
           * 1530497805902, total:1, pass:1, block:0 // run in slow qps
           * 1530497806905, total:3, pass:3, block:0
           * 1530497807909, total:2, pass:2, block:0
           * 1530497808913, total:3, pass:3, block:0
           * 1530497809917, total:269, pass:6, block:263 // request qps burst increase, warm up behavior triggered.
           * 1530497810917, total:3676, pass:7, block:3669
           * 1530497811919, total:3734, pass:9, block:3725
           * 1530497812920, total:3692, pass:9, block:3683
           * 1530497813923, total:3642, pass:10, block:3632
           * 1530497814926, total:3685, pass:10, block:3675
           * 1530497815930, total:3671, pass:11, block:3660
           * 1530497816933, total:3660, pass:15, block:3645
           * 1530497817936, total:3681, pass:21, block:3661 // warm up process end, pass qps increased to {@link FlowRule#count}
           * 1530497818940, total:3737, pass:20, block:3716
           * 1530497819945, total:3663, pass:20, block:3643
           * 1530497820950, total:3723, pass:21, block:3702
           * 1530497821954, total:3680, pass:20, block:3660
           * ...
           * 
          * * @author jialiang.linjl */ public class WarmUpFlowDemo { private static final String KEY = "abc"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 60 + 40; public static void main(String[] args) throws Exception { initFlowRule(); // trigger Sentinel internal init Entry entry = null; try { entry = SphU.entry(KEY); } catch (Exception e) { } finally { if (entry != null) { entry.exit(); } } Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); //first make the system run on a very low condition for (int i = 0; i < 3; i++) { Thread t = new Thread(new WarmUpTask()); t.setName("sentinel-warmup-task"); t.start(); } Thread.sleep(20000); /* * Start more thread to simulate more qps. Since we use {@link RuleConstant.CONTROL_BEHAVIOR_WARM_UP} as * {@link FlowRule#controlBehavior}, real passed qps will increase to {@link FlowRule#count} in * {@link FlowRule#warmUpPeriodSec} seconds. */ for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("sentinel-run-task"); t.start(); } } private static void initFlowRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); rule1.setWarmUpPeriodSec(10); rules.add(rule1); FlowRuleManager.loadRules(rules); } static class WarmUpTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(2000)); } catch (InterruptedException e) { // ignore } } } } static class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/WarmUpRateLimiterFlowDemo.java ================================================ package com.alibaba.csp.sentinel.demo.flow; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.util.TimeUtil; /** * When {@link FlowRule#controlBehavior} set to {@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER}, real passed * qps will gradually increase to {@link FlowRule#count}, other than burst increasing, and after the passed qps reaches * the threshold, the request will pass at a constant interval. *

          * In short, {@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER} behaves like * {@link RuleConstant#CONTROL_BEHAVIOR_WARM_UP} + {@link RuleConstant#CONTROL_BEHAVIOR_RATE_LIMITER}. *

          * *

          * Run this demo, results are as follows: *

           * ...
           * 1541035848056, total:5, pass:5, block:0 // run in slow qps
           * 1541035849061, total:0, pass:0, block:0
           * 1541035850066, total:6, pass:6, block:0
           * 1541035851068, total:2, pass:2, block:0
           * 1541035852073, total:3, pass:3, block:0
           * 1541035853078, total:3361, pass:7, block:3354 // request qps burst increase, warm up behavior triggered.
           * 1541035854083, total:3414, pass:7, block:3407
           * 1541035855087, total:3377, pass:7, block:3370
           * 1541035856091, total:3366, pass:8, block:3358
           * 1541035857096, total:3259, pass:8, block:3251
           * 1541035858101, total:3066, pass:13, block:3054
           * 1541035859105, total:3042, pass:15, block:3026
           * 1541035860109, total:2946, pass:17, block:2929
           * 1541035861113, total:2909, pass:20, block:2889 // warm up process end, pass qps increased to {@link FlowRule#count}
           * 1541035862117, total:2970, pass:20, block:2950
           * 1541035863122, total:2919, pass:20, block:2899
           * 1541035864127, total:2903, pass:21, block:2882
           * 1541035865133, total:2930, pass:20, block:2910
           * ...
           * 
          * * @author CarpenterLee * @see WarmUpFlowDemo * @see PaceFlowDemo */ public class WarmUpRateLimiterFlowDemo { private static final String KEY = "abc"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 100; public static void main(String[] args) throws Exception { initFlowRule(); // trigger Sentinel internal init Entry entry = null; try { entry = SphU.entry(KEY); } catch (Exception e) { } finally { if (entry != null) { entry.exit(); } } Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); //first make the system run on a very low condition for (int i = 0; i < 3; i++) { Thread t = new Thread(new SlowTask()); t.setName("sentinel-slow-task"); t.start(); } Thread.sleep(5000); // request qps burst increase, warm up behavior triggered. for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("sentinel-run-task"); t.start(); } } private static void initFlowRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(KEY); rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER); rule1.setWarmUpPeriodSec(10); rule1.setMaxQueueingTimeMs(100); rules.add(rule1); FlowRuleManager.loadRules(rules); } static class SlowTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(2000)); } catch (InterruptedException e) { // ignore } } } } static class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/system/SystemGuardDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.system; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; /** * @author jialiang.linjl */ public class SystemGuardDemo { private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 60 + 40; public static void main(String[] args) throws Exception { tick(); initSystemRule(); for (int i = 0; i < threadCount; i++) { Thread entryThread = new Thread(new Runnable() { @Override public void run() { while (true) { Entry entry = null; try { entry = SphU.entry("methodA", EntryType.IN); pass.incrementAndGet(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { // ignore } } catch (BlockException e1) { block.incrementAndGet(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { // ignore } } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } } } }); entryThread.setName("working-thread"); entryThread.start(); } } private static void initSystemRule() { SystemRule rule = new SystemRule(); // max load is 3 rule.setHighestSystemLoad(3.0); // max cpu usage is 60% rule.setHighestCpuUsage(0.6); // max avg rt of all request is 10 ms rule.setAvgRt(10); // max total qps is 20 rule.setQps(20); // max parallel working thread is 10 rule.setMaxThread(10); SystemRuleManager.loadRules(Collections.singletonList(rule)); } private static void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-cluster pom sentinel-demo-cluster-embedded sentinel-demo-cluster-server-alone ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/README.md ================================================ # Sentinel Cluster Embedded Mode Demo This demo demonstrates how to configure data source for cluster rules and configuration in **embedded mode**. You can start multiple `ClusterDemoApplication` instances and do cluster assignment in Sentinel dashboard or via dynamic data source. See [DemoClusterInitFunc](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterInitFunc.java) for a sample of dynamic configuration. ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/pom.xml ================================================ com.alibaba.csp sentinel-demo-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-cluster-embedded 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-cluster-client-default ${project.version} com.alibaba.csp sentinel-cluster-server-default ${project.version} com.alibaba.csp sentinel-datasource-nacos com.alibaba.csp sentinel-annotation-aspectj org.springframework.boot spring-boot-starter-aop ${spring.boot.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster; /** * @author Eric Zhao */ public final class DemoConstants { public static final String FLOW_POSTFIX = "-flow-rules"; public static final String PARAM_FLOW_POSTFIX = "-param-rules"; public static final String SERVER_NAMESPACE_SET_POSTFIX = "-cs-namespace-set"; public static final String CLIENT_CONFIG_POSTFIX = "-cc-config"; public static final String CLUSTER_MAP_POSTFIX = "-cluster-map"; private DemoConstants() {} } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/app/ClusterDemoApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Eric Zhao */ @SpringBootApplication public class ClusterDemoApplication { public static void main(String[] args) { SpringApplication.run(ClusterDemoApplication.class, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/app/config/AopConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.app.config; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * AOP config to enable annotation support for Sentinel. * * @author Eric Zhao */ @Configuration public class AopConfig { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/app/controller/ClusterDemoController.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.app.controller; import com.alibaba.csp.sentinel.demo.cluster.app.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author Eric Zhao */ @RestController public class ClusterDemoController { @Autowired private DemoService service; @GetMapping("/hello/{name}") public String apiHello(@PathVariable String name) throws Exception { return service.sayHello(name); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/app/service/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.app.service; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.stereotype.Service; /** * @author Eric Zhao */ @Service public class DemoService { @SentinelResource(blockHandler = "sayHelloBlockHandler") public String sayHello(String name) { return "Hello, " + name; } public String sayHelloBlockHandler(String name, BlockException ex) { // This is the block handler. ex.printStackTrace(); return String.format("Oops, <%s> blocked by Sentinel", name); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/entity/ClusterGroupEntity.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.entity; import java.util.Set; /** * @author Eric Zhao * @since 1.4.1 */ public class ClusterGroupEntity { private String machineId; private String ip; private Integer port; private Set clientSet; public String getMachineId() { return machineId; } public ClusterGroupEntity setMachineId(String machineId) { this.machineId = machineId; return this; } public String getIp() { return ip; } public ClusterGroupEntity setIp(String ip) { this.ip = ip; return this; } public Integer getPort() { return port; } public ClusterGroupEntity setPort(Integer port) { this.port = port; return this; } public Set getClientSet() { return clientSet; } public ClusterGroupEntity setClientSet(Set clientSet) { this.clientSet = clientSet; return this; } @Override public String toString() { return "ClusterGroupEntity{" + "machineId='" + machineId + '\'' + ", ip='" + ip + '\'' + ", port=" + port + ", clientSet=" + clientSet + '}'; } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.init; import java.util.List; import java.util.Objects; import java.util.Optional; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; import com.alibaba.csp.sentinel.demo.cluster.entity.ClusterGroupEntity; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** * @author Eric Zhao */ public class DemoClusterInitFunc implements InitFunc { private static final String APP_NAME = AppNameUtil.getAppName(); private final String remoteAddress = "localhost:8848"; private final String groupId = "SENTINEL_GROUP"; private final String flowDataId = APP_NAME + DemoConstants.FLOW_POSTFIX; private final String paramDataId = APP_NAME + DemoConstants.PARAM_FLOW_POSTFIX; private final String configDataId = APP_NAME + "-cluster-client-config"; private final String clusterMapDataId = APP_NAME + DemoConstants.CLUSTER_MAP_POSTFIX; @Override public void init() throws Exception { // Register client dynamic rule data source. initDynamicRuleProperty(); // Register token client related data source. // Token client common config: initClientConfigProperty(); // Token client assign config (e.g. target token server) retrieved from assign map: initClientServerAssignProperty(); // Register token server related data source. // Register dynamic rule data source supplier for token server: registerClusterRuleSupplier(); // Token server transport config extracted from assign map: initServerTransportConfigProperty(); // Init cluster state property for extracting mode from cluster map data source. initStateProperty(); } private void initDynamicRuleProperty() { ReadableDataSource> ruleSource = new NacosDataSource<>(remoteAddress, groupId, flowDataId, source -> JSON.parseObject(source, new TypeReference>() {})); FlowRuleManager.register2Property(ruleSource.getProperty()); ReadableDataSource> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId, paramDataId, source -> JSON.parseObject(source, new TypeReference>() {})); ParamFlowRuleManager.register2Property(paramRuleSource.getProperty()); } private void initClientConfigProperty() { ReadableDataSource clientConfigDs = new NacosDataSource<>(remoteAddress, groupId, configDataId, source -> JSON.parseObject(source, new TypeReference() {})); ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty()); } private void initServerTransportConfigProperty() { ReadableDataSource serverTransportDs = new NacosDataSource<>(remoteAddress, groupId, clusterMapDataId, source -> { List groupList = JSON.parseObject(source, new TypeReference>() {}); return Optional.ofNullable(groupList) .flatMap(this::extractServerTransportConfig) .orElse(null); }); ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty()); } private void registerClusterRuleSupplier() { // Register cluster flow rule property supplier which creates data source by namespace. // Flow rule dataId format: ${namespace}-flow-rules ClusterFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, namespace + DemoConstants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {})); return ds.getProperty(); }); // Register cluster parameter flow rule property supplier which creates data source by namespace. ClusterParamFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, namespace + DemoConstants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {})); return ds.getProperty(); }); } private void initClientServerAssignProperty() { // Cluster map format: // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}] // machineId: , commandPort for port exposed to Sentinel dashboard (transport module) ReadableDataSource clientAssignDs = new NacosDataSource<>(remoteAddress, groupId, clusterMapDataId, source -> { List groupList = JSON.parseObject(source, new TypeReference>() {}); return Optional.ofNullable(groupList) .flatMap(this::extractClientAssignment) .orElse(null); }); ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty()); } private void initStateProperty() { // Cluster map format: // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}] // machineId: , commandPort for port exposed to Sentinel dashboard (transport module) ReadableDataSource clusterModeDs = new NacosDataSource<>(remoteAddress, groupId, clusterMapDataId, source -> { List groupList = JSON.parseObject(source, new TypeReference>() {}); return Optional.ofNullable(groupList) .map(this::extractMode) .orElse(ClusterStateManager.CLUSTER_NOT_STARTED); }); ClusterStateManager.registerProperty(clusterModeDs.getProperty()); } private int extractMode(List groupList) { // If any server group machineId matches current, then it's token server. if (groupList.stream().anyMatch(this::machineEqual)) { return ClusterStateManager.CLUSTER_SERVER; } // If current machine belongs to any of the token server group, then it's token client. // Otherwise it's unassigned, should be set to NOT_STARTED. boolean canBeClient = groupList.stream() .flatMap(e -> e.getClientSet().stream()) .filter(Objects::nonNull) .anyMatch(e -> e.equals(getCurrentMachineId())); return canBeClient ? ClusterStateManager.CLUSTER_CLIENT : ClusterStateManager.CLUSTER_NOT_STARTED; } private Optional extractServerTransportConfig(List groupList) { return groupList.stream() .filter(this::machineEqual) .findAny() .map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600)); } private Optional extractClientAssignment(List groupList) { if (groupList.stream().anyMatch(this::machineEqual)) { return Optional.empty(); } // Build client assign config from the client set of target server group. for (ClusterGroupEntity group : groupList) { if (group.getClientSet().contains(getCurrentMachineId())) { String ip = group.getIp(); Integer port = group.getPort(); return Optional.of(new ClusterClientAssignConfig(ip, port)); } } return Optional.empty(); } private boolean machineEqual(/*@Valid*/ ClusterGroupEntity group) { return getCurrentMachineId().equals(group.getMachineId()); } private String getCurrentMachineId() { // Note: this may not work well for container-based env. return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getRuntimePort(); } private static final String SEPARATOR = "@"; } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterInitFunc ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-server-alone/pom.xml ================================================ com.alibaba.csp sentinel-demo-cluster ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-cluster-server-alone com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-cluster-server-default ${project.version} com.alibaba.csp sentinel-datasource-nacos ch.qos.logback logback-classic 1.2.11 ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-server-alone/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster; import java.util.Collections; import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer; import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; /** *

          Cluster server demo (alone mode).

          *

          Here we init the cluster server dynamic data sources in * {@link com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc}.

          * * @author Eric Zhao * @since 1.4.0 */ public class ClusterServerDemo { public static void main(String[] args) throws Exception { // Not embedded mode by default (alone mode). ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); // A sample for manually load config for cluster server. // It's recommended to use dynamic data source to cluster manage config and rules. // See the sample in DemoClusterServerInitFunc for detail. ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() .setIdleSeconds(600) .setPort(11111)); ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(DemoConstants.APP_NAME)); // Start the server. tokenServer.start(); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-server-alone/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster; /** * @author Eric Zhao */ public final class DemoConstants { public static final String APP_NAME = "appA"; public static final String FLOW_POSTFIX = "-flow-rules"; public static final String PARAM_FLOW_POSTFIX = "-param-rules"; private DemoConstants() {} } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-server-alone/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.cluster.init; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** * @author Eric Zhao */ public class DemoClusterServerInitFunc implements InitFunc { private final String remoteAddress = "localhost:8848"; private final String groupId = "SENTINEL_GROUP"; private final String namespaceSetDataId = "cluster-server-namespace-set"; private final String serverTransportDataId = "cluster-server-transport-config"; @Override public void init() throws Exception { // Register cluster flow rule property supplier which creates data source by namespace. ClusterFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, namespace + DemoConstants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {})); return ds.getProperty(); }); // Register cluster parameter flow rule property supplier. ClusterParamFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, namespace + DemoConstants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {})); return ds.getProperty(); }); // Server namespace set (scope) data source. ReadableDataSource> namespaceDs = new NacosDataSource<>(remoteAddress, groupId, namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference>() {})); ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty()); // Server transport configuration data source. ReadableDataSource transportConfigDs = new NacosDataSource<>(remoteAddress, groupId, serverTransportDataId, source -> JSON.parseObject(source, new TypeReference() {})); ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty()); } } ================================================ FILE: sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-server-alone/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-command-handler com.alibaba.csp sentinel-transport-simple-http ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/java/com/alibaba/csp/sentinel/demo/commandhandler/CommandDemo.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.commandhandler; import com.alibaba.csp.sentinel.init.InitExecutor; /** *

          To run this demo, we need to add the {@code sentinel-transport-simple-http} dependency.

          *

          * As soon as the CommandCenter has been initialized, we can visit {@code http://ip:commandPort/api} * to see all available command APIs (by default the port is 8719). * We can also visit our customized {@code /echo} command. *

          * * @author Eric Zhao */ public class CommandDemo { public static void main(String[] args) { // Only for demo. You don't have to do this in your application. InitExecutor.doInit(); System.out.println("Sentinel CommandCenter has been initialized"); } } ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/java/com/alibaba/csp/sentinel/demo/commandhandler/EchoCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.commandhandler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; /** * This class is a demo shows how to create and register a customized CommandHandler. * *
            *
          • 1. Create a class which implements the {@link CommandHandler} SPI interface
          • *
          • 2. Use a {@link CommandMapping} to specify the url and desc of your CommandHandler
          • *
          • 3. Implement your own {@code handle} method
          • *
          • 4. Add your CommandHandler in {@code com.alibaba.csp.sentinel.command.CommandHandler} file which is stored in * {@code resources/META-INF/services/} directory
          • *
          * * @author houyi */ @CommandMapping(name = "echo", desc = "echo command for demo") public class EchoCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String name = request.getParam("name"); if (name == null || name.trim().length() == 0) { return CommandResponse.ofSuccess("Tell us what's your name by submit a name parameter"); } return CommandResponse.ofSuccess("Hello: " + name); } } ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/java/com/alibaba/csp/sentinel/demo/commandhandler/interceptor/AllCommandHandlerInterceptor.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.commandhandler.interceptor; import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandRequestExecution; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.spi.Spi; /** * @author icodening * @date 2022.03.23 */ @Spi(order = Spi.ORDER_HIGHEST) public class AllCommandHandlerInterceptor implements CommandHandlerInterceptor { @Override public boolean shouldIntercept(String commandName) { return true; } @Override public CommandResponse intercept(CommandRequest request, CommandRequestExecution execution) { System.out.println("[AllCommandHandlerInterceptor] start"); long begin = System.currentTimeMillis(); try { return execution.execute(request); } catch (Throwable throwable) { System.out.println("[AllCommandHandlerInterceptor] catch exception: " + throwable.getMessage()); throw throwable; } finally { long cost = System.currentTimeMillis() - begin; System.out.println("[AllCommandHandlerInterceptor] complete, cost " + cost + "ms"); } } } ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/java/com/alibaba/csp/sentinel/demo/commandhandler/interceptor/EchoCommandHandlerInterceptor.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.commandhandler.interceptor; import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandRequestExecution; import com.alibaba.csp.sentinel.command.CommandResponse; /** * @author icodening * @date 2022.03.23 */ public class EchoCommandHandlerInterceptor implements CommandHandlerInterceptor { @Override public boolean shouldIntercept(String commandName) { return "echo".equals(commandName); } @Override public CommandResponse intercept(CommandRequest request, CommandRequestExecution execution) { System.out.println("[EchoCommandHandlerInterceptor] intercept 'echo' command,all params is " + request.getParameters() + ""); CommandResponse response = execution.execute(request); return CommandResponse.ofSuccess("intercept result : [" + response.getResult() + "]"); } } ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.demo.commandhandler.EchoCommandHandler ================================================ FILE: sentinel-demo/sentinel-demo-command-handler/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandlerInterceptor ================================================ com.alibaba.csp.sentinel.demo.commandhandler.interceptor.EchoCommandHandlerInterceptor com.alibaba.csp.sentinel.demo.commandhandler.interceptor.AllCommandHandlerInterceptor ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/README.md ================================================ # Sentinel Dubbo Demo Sentinel 提供了与 Dubbo 整合的模块 - Sentinel Dubbo Adapter,主要包括针对 Service Provider 和 Service Consumer 实现的 Filter。使用时用户只需引入以下模块(以 Maven 为例): ```xml com.alibaba.csp sentinel-dubbo-adapter x.y.z ``` 引入此依赖后,Dubbo 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。 > **注:若希望接入 Dashboard,请参考后面接入控制台的步骤。只引入 Sentinel Dubbo Adapter 无法接入控制台!** 若不希望开启 Sentinel Dubbo Adapter 中的某个 Filter,可以手动关闭对应的 Filter,比如: ```java @Bean public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setFilter("-sentinel.dubbo.consumer.filter"); return consumerConfig; } ``` 我们提供了几个具体的 Demo 来分别演示 Provider 和 Consumer 的限流场景。 ## Service Provider Service Provider 用于向外界提供服务,处理各个消费者的调用请求。为了保护 Provider 不被激增的流量拖垮影响稳定性,可以给 Provider 配置 **QPS 模式**的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝多的请求。限流粒度可以是服务接口和服务方法两种粒度。若希望整个服务接口的 QPS 不超过一定数值,则可以为对应服务接口资源(resourceName 为**接口全限定名**)配置 QPS 阈值;若希望服务的某个方法的 QPS 不超过一定数值,则可以为对应服务方法资源(resourceName 为**接口全限定名:方法签名**)配置 QPS 阈值。有关配置详情请参考 [流量控制 | Sentinel]()。 Demo 1 演示了此限流场景,我们看一下这种模式的限流产生的效果。假设我们已经定义了某个服务接口 `com.alibaba.csp.sentinel.demo.dubbo.FooService`,其中有一个方法 `sayHello(java.lang.String)`,Provider 端该方法设定 QPS 阈值为 10。在 Consumer 端在 1s 之内连续发起 15 次调用,可以通过日志文件看到 Provider 端被限流。拦截日志统一记录在 `~/logs/csp/sentinel-block.log` 中: ```plaintext 2018-07-24 17:13:43|1|com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String),FlowException,default,|5,0 ``` 在 Provider 对应的 metrics 日志中也有记录: ```plaintext 1532423623000|2018-07-24 17:13:43|com.alibaba.csp.sentinel.demo.dubbo.FooService|15|0|15|0|3 1532423623000|2018-07-24 17:13:43|com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)|10|5|10|0|0 ``` 很多场景下,根据**调用方**来限流也是非常重要的。比如有两个服务 A 和 B 都向 Service Provider 发起调用请求,我们希望只对来自服务 B 的请求进行限流,则可以设置限流规则的 `limitApp` 为服务 B 的名称。Sentinel Dubbo Adapter 会自动解析 Dubbo 消费者(调用方)的 application name 作为调用方名称(`origin`),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(`default`),则该限流规则对所有调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。 > 注:Dubbo 默认通信不携带对端 application name 信息,因此需要开发者在调用端手动将 application name 置入 attachment 中,provider 端进行相应的解析。Sentinel Dubbo Adapter 实现了一个 Filter 用于自动从 consumer 端向 provider 端透传 application name。若调用端未引入 Sentinel Dubbo Adapter,又希望根据调用端限流,可以在调用端手动将 application name 置入 attachment 中,key 为 `dubboApplication`。 在限流日志中会也会记录调用方的名称,如: ```plaintext 2018-07-25 16:26:48|1|com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String),FlowException,default,demo-consumer|5,0 ``` 其中日志中的 `demo-consumer` 即为调用方名称。 ## Service Consumer > 对服务消费方的流量控制可分为**控制并发线程数**和**服务降级**两个维度。 ### 并发线程数限流 Service Consumer 作为客户端去调用远程服务。每一个服务都可能会依赖几个下游服务,若某个服务 A 依赖的下游服务 B 出现了不稳定的情况,服务 A 请求服务 B 的响应时间变长,从而服务 A 调用服务 B 的线程就会产生堆积,最终可能耗尽服务 A 的线程数。我们通过用并发线程数来控制对下游服务 B 的访问,来保证下游服务不可靠的时候,不会拖垮服务自身。基于这种场景,推荐给 Consumer 配置**线程数模式**的限流,来保证自身不被不稳定服务所影响。限流粒度同样可以是服务接口和服务方法两种粒度。 采用基于线程数的限流模式后,我们不需要再显式地去进行线程池隔离,Sentinel 会控制资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成。 Demo 2 演示了此限流场景,我们看一下这种模式的效果。假设当前服务 A 依赖两个远程服务方法 `sayHello(java.lang.String)` 和 `doAnother()`。前者远程调用的响应时间 为 1s-1.5s之间,后者 RT 非常小(30 ms 左右)。服务 A 端设两个远程方法 thread count 为 5。然后每隔 50 ms 左右向线程池投入两个任务,作为消费者分别远程调用对应方法,持续 10 次。可以看到 `sayHello` 方法被限流 5 次,因为后面调用的时候前面的远程调用还未返回(RT 高);而 `doAnother()` 调用则不受影响。线程数目超出时快速失败能够有效地防止自己被慢调用所影响。 ### 服务降级 当服务依赖于多个下游服务,而某个下游服务调用非常慢时,会严重影响当前服务的调用。这里我们可以利用 Sentinel 熔断降级的功能,为调用端配置基于平均 RT 的[降级规则]()。这样当调用链路中某个服务调用的平均 RT 升高,在一定的次数内超过配置的 RT 阈值,Sentinel 就会对此调用资源进行降级操作,接下来的调用都会立刻拒绝,直到过了一段设定的时间后才恢复,从而保护服务不被调用端短板所影响。同时可以配合 fallback 功能使用,在被降级的时候提供相应的处理逻辑。 ## Fallback 从 0.1.1 版本开始,Sentinel Dubbo Adapter 还支持配置全局的 fallback 函数,可以在 Dubbo 服务被限流/降级/负载保护的时候进行相应的 fallback 处理。用户只需要实现自定义的 [`DubboFallback`](https://github.com/alibaba/Sentinel/blob/master/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallback.java) 接口,并通过 `DubboFallbackRegistry` 注册即可。默认情况会直接将 `BlockException` 包装后抛出。同时,我们还可以配合 [Dubbo 的 fallback 机制](http://dubbo.apache.org/#!/docs/user/demos/local-mock.md?lang=zh-cn) 来为降级的服务提供替代的实现。 Demo 2 的 Consumer 端提供了一个简单的 fallback 示例。 ## Sentinel Dashboard Sentinel 还提供 API 用于获取实时的监控信息,对应文档见[此处]()。为了便于使用,Sentinel 还提供了一个控制台(Dashboard)用于配置规则、查看监控、机器发现等功能。 接入 Dashboard 的步骤(**缺一不可**): 1. 按照 [Sentinel 控制台文档]() 启动控制台 2. 应用引入 `sentinel-transport-simple-http` 依赖,以便控制台可以拉取对应应用的相关信息 3. 给应用添加相关的启动参数,启动应用。需要配置的参数有: - `-Dcsp.sentinel.api.port`:客户端的 port,用于上报相关信息 - `-Dcsp.sentinel.dashboard.server`:控制台的地址 - `-Dproject.name`:应用名称,会在控制台中显示 注意某些环境下本地运行 Dubbo 服务还需要加上 `-Djava.net.preferIPv4Stack=true` 参数。比如 Service Provider 示例的启动参数: ```bash -Djava.net.preferIPv4Stack=true -Dcsp.sentinel.api.port=8720 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=dubbo-provider-demo ``` Service Consumer 示例的启动参数: ```bash -Djava.net.preferIPv4Stack=true -Dcsp.sentinel.api.port=8721 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=dubbo-consumer-demo ``` 这样在启动 Service Provider 和 Service Consumer 示例以后,就可以在 Sentinel 控制台中找到我们的服务了。可以很方便地在控制台中配置限流规则: ![规则配置](http://dubbo.incubator.apache.org/img/blog/sentinel-dashboard-view-rules.png) 或者查看实时监控数据: ![秒级实时监控](http://dubbo.incubator.apache.org/img/blog/sentinel-dashboard-metrics.png) ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-dubbo com.alibaba dubbo 2.6.12 io.netty netty-all 4.1.75.Final com.alibaba.spring spring-context-support 1.0.2 com.alibaba.csp sentinel-dubbo-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ${project.version} ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/FooService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo; /** * @author Eric Zhao */ public interface FooService { String sayHello(String name); String doAnother(); } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/consumer/ConsumerConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.consumer; import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ConsumerConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration @DubboComponentScan public class ConsumerConfiguration { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("demo-consumer"); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("multicast://224.5.6.7:1234"); return registryConfig; } @Bean public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); // Uncomment below line if you don't want to enable Sentinel for Dubbo service consumers. // consumerConfig.setFilter("-sentinel.dubbo.consumer.filter"); return consumerConfig; } @Bean public FooServiceConsumer annotationDemoServiceConsumer() { return new FooServiceConsumer(); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/consumer/FooServiceConsumer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.consumer; import com.alibaba.csp.sentinel.demo.dubbo.FooService; import com.alibaba.dubbo.config.annotation.Reference; /** * @author Eric Zhao */ public class FooServiceConsumer { @Reference(url = "dubbo://127.0.0.1:25758", timeout = 3000) private FooService fooService; public String sayHello(String name) { return fooService.sayHello(name); } public String doAnother() { return fooService.doAnother(); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo1/FooConsumerBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo1; import com.alibaba.csp.sentinel.demo.dubbo.consumer.ConsumerConfiguration; import com.alibaba.csp.sentinel.demo.dubbo.consumer.FooServiceConsumer; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8721
           * -Dproject.name=dubbo-consumer-demo
           * 
          * * @author Eric Zhao */ public class FooConsumerBootstrap { public static void main(String[] args) { AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext(); consumerContext.register(ConsumerConfiguration.class); consumerContext.refresh(); FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class); for (int i = 0; i < 15; i++) { try { String message = service.sayHello("Eric"); System.out.println("Success: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } } } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo1/FooProviderBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo1; import java.util.Collections; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8720
           * -Dproject.name=dubbo-provider-demo
           * 
          * * @author Eric Zhao */ public class FooProviderBootstrap { private static final String RES_KEY = "com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)"; private static final String INTERFACE_RES_KEY = "com.alibaba.csp.sentinel.demo.dubbo.FooService"; public static void main(String[] args) { // Users don't need to manually call this method. InitExecutor.doInit(); initFlowRule(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(ProviderConfiguration.class); context.refresh(); System.out.println("Service provider is ready"); } private static void initFlowRule() { FlowRule flowRule = new FlowRule(); flowRule.setResource(RES_KEY); flowRule.setCount(10); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo1/FooServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo1; import java.time.LocalDateTime; import com.alibaba.csp.sentinel.demo.dubbo.FooService; import com.alibaba.dubbo.config.annotation.Service; /** * @author Eric Zhao */ @Service public class FooServiceImpl implements FooService { @Override public String sayHello(String name) { return String.format("Hello, %s at %s", name, LocalDateTime.now()); } @Override public String doAnother() { return LocalDateTime.now().toString(); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo1/ProviderConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo1; import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration @DubboComponentScan("com.alibaba.csp.sentinel.demo.dubbo.demo1") public class ProviderConfiguration { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("demo-provider"); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("multicast://224.5.6.7:1234"); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(25758); return protocolConfig; } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo2/FooConsumerBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo2; import java.util.Collections; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.demo.dubbo.consumer.ConsumerConfiguration; import com.alibaba.csp.sentinel.demo.dubbo.consumer.FooServiceConsumer; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.dubbo.rpc.RpcResult; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8721
           * -Dproject.name=dubbo-consumer-demo
           * 
          * * @author Eric Zhao */ public class FooConsumerBootstrap { private static final String RES_KEY = "com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)"; private static final String INTERFACE_RES_KEY = "com.alibaba.csp.sentinel.demo.dubbo.FooService"; @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ExecutorService pool = Executors.newFixedThreadPool(10, new NamedThreadFactory("dubbo-consumer-pool")); public static void main(String[] args) { initFlowRule(); AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext(); consumerContext.register(ConsumerConfiguration.class); consumerContext.refresh(); FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class); for (int i = 0; i < 10; i++) { pool.submit(() -> { try { String message = service.sayHello("Eric"); System.out.println("Success: " + message); } catch (SentinelRpcException ex) { System.out.println("Blocked"); } catch (Exception ex) { ex.printStackTrace(); } }); pool.submit(() -> System.out.println("Another: " + service.doAnother())); } } private static void initFlowRule() { FlowRule flowRule = new FlowRule(); flowRule.setResource(RES_KEY); flowRule.setCount(5); flowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); flowRule.setLimitApp("default"); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); } private static void registerFallback() { // Register fallback handler for consumer. // If you only want to handle degrading, you need to // check the type of BlockException. DubboAdapterGlobalConfig.setConsumerFallback((a, b, ex) -> new RpcResult("Error: " + ex.getClass().getTypeName())); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo2/FooProviderBootstrap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo2; import com.alibaba.csp.sentinel.init.InitExecutor; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * Please add the following VM arguments: *
           * -Djava.net.preferIPv4Stack=true
           * -Dcsp.sentinel.api.port=8720
           * -Dproject.name=dubbo-provider-demo
           * 
          * * @author Eric Zhao */ public class FooProviderBootstrap { public static void main(String[] args) { // Users don't need to manually call this method. InitExecutor.doInit(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(ProviderConfiguration.class); context.refresh(); System.out.println("Service provider is ready"); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo2/FooServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo2; import java.time.LocalDateTime; import com.alibaba.csp.sentinel.demo.dubbo.FooService; import com.alibaba.dubbo.config.annotation.Service; /** * @author Eric Zhao */ @Service public class FooServiceImpl implements FooService { @Override public String sayHello(String name) { return String.format("Hello, %s at %s", name, LocalDateTime.now()); } @Override public String doAnother() { return LocalDateTime.now().toString(); } } ================================================ FILE: sentinel-demo/sentinel-demo-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/dubbo/demo2/ProviderConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.dubbo.demo2; import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration @DubboComponentScan("com.alibaba.csp.sentinel.demo.dubbo.demo2") public class ProviderConfiguration { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("demo-provider"); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("multicast://224.5.6.7:1234"); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(25758); return protocolConfig; } } ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-demo ${revision} ../pom.xml sentinel-demo-dynamic-file-rule com.alibaba.csp sentinel-core com.alibaba.csp sentinel-datasource-extension com.alibaba.csp sentinel-transport-simple-http com.alibaba fastjson ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/java/com/alibaba/csp/sentinel/demo/file/rule/FileDataSourceDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.file.rule; import java.net.URLDecoder; import java.util.List; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** *

          * This Demo shows how to use {@link FileRefreshableDataSource} to read {@link Rule}s from file. The * {@link FileRefreshableDataSource} will automatically fetches the backend file every 3 seconds, and * inform the listener if the file is updated. *

          *

          * Each {@link ReadableDataSource} has a {@link SentinelProperty} to hold the deserialized config data. * {@link PropertyListener} will listen to the {@link SentinelProperty} instead of the datasource. * {@link Converter} is used for telling how to deserialize the data. *

          *

          * {@link FlowRuleManager#register2Property(SentinelProperty)}, * {@link DegradeRuleManager#register2Property(SentinelProperty)}, * {@link SystemRuleManager#register2Property(SentinelProperty)} could be called for listening the * {@link Rule}s change. *

          *

          * For other kinds of data source, such as Nacos, * Zookeeper, Git, or even CSV file, We could implement {@link ReadableDataSource} interface to read these * configs. *

          * * @author Carpenter Lee * @author Eric Zhao */ public class FileDataSourceDemo { public static void main(String[] args) throws Exception { FileDataSourceDemo demo = new FileDataSourceDemo(); demo.listenRules(); /* * Start to require tokens, rate will be limited by rule in FlowRule.json */ FlowQpsRunner runner = new FlowQpsRunner(); runner.simulateTraffic(); runner.tick(); } private void listenRules() throws Exception { ClassLoader classLoader = getClass().getClassLoader(); String flowRulePath = URLDecoder.decode(classLoader.getResource("FlowRule.json").getFile(), "UTF-8"); String degradeRulePath = URLDecoder.decode(classLoader.getResource("DegradeRule.json").getFile(), "UTF-8"); String systemRulePath = URLDecoder.decode(classLoader.getResource("SystemRule.json").getFile(), "UTF-8"); // Data source for FlowRule FileRefreshableDataSource> flowRuleDataSource = new FileRefreshableDataSource<>( flowRulePath, flowRuleListParser); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); // Data source for DegradeRule FileRefreshableDataSource> degradeRuleDataSource = new FileRefreshableDataSource<>( degradeRulePath, degradeRuleListParser); DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty()); // Data source for SystemRule FileRefreshableDataSource> systemRuleDataSource = new FileRefreshableDataSource<>( systemRulePath, systemRuleListParser); SystemRuleManager.register2Property(systemRuleDataSource.getProperty()); } private Converter> flowRuleListParser = source -> JSON.parseObject(source, new TypeReference>() {}); private Converter> degradeRuleListParser = source -> JSON.parseObject(source, new TypeReference>() {}); private Converter> systemRuleListParser = source -> JSON.parseObject(source, new TypeReference>() {}); } ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/java/com/alibaba/csp/sentinel/demo/file/rule/FileDataSourceInit.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.file.rule; import java.io.File; import java.util.List; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.datasource.FileWritableDataSource; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** *

          * A sample showing how to register readable and writable data source via Sentinel init SPI mechanism. *

          *

          * To activate this, you can add the class name to `com.alibaba.csp.sentinel.init.InitFunc` file * in `META-INF/services/` directory of the resource directory. Then the data source will be automatically * registered during the initialization of Sentinel. *

          * * @author Eric Zhao */ public class FileDataSourceInit implements InitFunc { @Override public void init() throws Exception { // A fake path. String flowRuleDir = System.getProperty("user.home") + File.separator + "sentinel" + File.separator + "rules"; String flowRuleFile = "flowRule.json"; String flowRulePath = flowRuleDir + File.separator + flowRuleFile; ReadableDataSource> ds = new FileRefreshableDataSource<>( flowRulePath, source -> JSON.parseObject(source, new TypeReference>() {}) ); // Register to flow rule manager. FlowRuleManager.register2Property(ds.getProperty()); WritableDataSource> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson); // Register to writable data source registry so that rules can be updated to file // when there are rules pushed from the Sentinel Dashboard. WritableDataSourceRegistry.registerFlowDataSource(wds); } private String encodeJson(T t) { return JSON.toJSONString(t); } } ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/java/com/alibaba/csp/sentinel/demo/file/rule/FlowQpsRunner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.file.rule; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Flow Rule demo. * * @author Carpenter Lee */ class FlowQpsRunner { private static final String KEY = "abc"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 1; private static int seconds = 60 + 40; public void simulateTraffic() { for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("simulate-traffic-Task"); t.start(); } } public void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static final class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(KEY); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } static final class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + " send qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/java/com/alibaba/csp/sentinel/demo/file/rule/JarFileDataSourceDemo.java ================================================ package com.alibaba.csp.sentinel.demo.file.rule; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.FileInJarReadableDataSource; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import java.util.List; /** *

          * This Demo shows how to use {@link FileInJarReadableDataSource} to read {@link Rule}s from jarfile. The * {@link FileInJarReadableDataSource} will automatically fetches the backend file every 3 seconds, and * inform the listener if the file is updated. *

          *

          * Each {@link ReadableDataSource} has a {@link SentinelProperty} to hold the deserialized config data. * {@link PropertyListener} will listen to the {@link SentinelProperty} instead of the datasource. * {@link Converter} is used for telling how to deserialize the data. *

          *

          * {@link FlowRuleManager#register2Property(SentinelProperty)}, * {@link DegradeRuleManager#register2Property(SentinelProperty)}, * {@link SystemRuleManager#register2Property(SentinelProperty)} could be called for listening the * {@link Rule}s change. *

          *

          * For other kinds of data source, such as Nacos, * Zookeeper, Git, or even CSV file, We could implement {@link ReadableDataSource} interface to read these * configs. *

          * * @author dingq */ public class JarFileDataSourceDemo { public static void main(String[] args) throws Exception { JarFileDataSourceDemo demo = new JarFileDataSourceDemo(); demo.listenRules(); // Start to require tokens, rate will be limited by rule of FlowRule.json in jar. FlowQpsRunner runner = new FlowQpsRunner(); runner.simulateTraffic(); runner.tick(); } private void listenRules() throws Exception { // Modify the path with your real path. String jarPath = System.getProperty("user.dir") + "/sentinel-demo/sentinel-demo-dynamic-file-rule/target/" + "sentinel-demo-dynamic-file-rule.jar"; // eg: if flowRuleInJarName full path is 'sentinel-demo-dynamic-file-rule.jar!/classes/FlowRule.json', // your flowRuleInJarName is 'classes/FlowRule.json' String flowRuleInJarPath = "FlowRule.json"; FileInJarReadableDataSource> flowRuleDataSource = new FileInJarReadableDataSource<>( jarPath,flowRuleInJarPath, flowRuleListParser); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } private Converter> flowRuleListParser = source -> JSON.parseObject(source, new TypeReference>() {}); } ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/resources/DegradeRule.json ================================================ [ { "resource": "abc0", "count": 20.0, "grade": 0, "passCount": 0, "timeWindow": 10 }, { "resource": "abc1", "count": 15.0, "grade": 0, "passCount": 0, "timeWindow": 10 } ] ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/resources/FlowRule.json ================================================ [ { "resource": "abc", "controlBehavior": 0, "count": 20.0, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "abc1", "controlBehavior": 0, "count": 20.0, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: sentinel-demo/sentinel-demo-dynamic-file-rule/src/main/resources/SystemRule.json ================================================ [ { "avgRt": 10, "highestSystemLoad": 5.0, "maxThread": 10, "qps": 20.0 } ] ================================================ FILE: sentinel-demo/sentinel-demo-etcd-datasource/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-etcd-datasource com.alibaba.csp sentinel-core com.alibaba.csp sentinel-datasource-etcd com.alibaba fastjson ================================================ FILE: sentinel-demo/sentinel-demo-etcd-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/etcd/EtcdConfigSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.etcd; import io.etcd.jetcd.ByteSequence; import io.etcd.jetcd.Client; /** * Etcd config sender for demo. * * @author lianglin * @since 1.7.0 */ public class EtcdConfigSender { public static void main(String[] args) throws InterruptedException { String rule_key = "sentinel_demo_rule_key"; Client client = Client.builder() .endpoints("http://127.0.0.1:2379") .user(ByteSequence.from("root".getBytes())) .password(ByteSequence.from("12345".getBytes())) .build(); final String rule = "[\n" + " {\n" + " \"resource\": \"TestResource\",\n" + " \"controlBehavior\": 0,\n" + " \"count\": 5.0,\n" + " \"grade\": 1,\n" + " \"limitApp\": \"default\",\n" + " \"strategy\": 0\n" + " }\n" + "]"; client.getKVClient() .put(ByteSequence.from(rule_key.getBytes()), ByteSequence.from(rule.getBytes())); System.out.println("setting rule success"); Thread.sleep(10000); } } ================================================ FILE: sentinel-demo/sentinel-demo-etcd-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/etcd/EtcdDataSourceDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.etcd; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.etcd.EtcdConfig; import com.alibaba.csp.sentinel.datasource.etcd.EtcdDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import java.util.List; /** * @author lianglin * @since 1.7.0 */ public class EtcdDataSourceDemo { public static void main(String[] args) { String rule_key = "sentinel_demo_rule_key"; String yourUserName = "root"; String yourPassWord = "12345"; String endPoints = "http://127.0.0.1:2379"; SentinelConfig.setConfig(EtcdConfig.END_POINTS, endPoints); SentinelConfig.setConfig(EtcdConfig.USER, yourUserName); SentinelConfig.setConfig(EtcdConfig.PASSWORD, yourPassWord); SentinelConfig.setConfig(EtcdConfig.CHARSET, "utf-8"); SentinelConfig.setConfig(EtcdConfig.AUTH_ENABLE, "true"); ReadableDataSource> flowRuleEtcdDataSource = new EtcdDataSource<>(rule_key, (rule) -> JSON.parseArray(rule, FlowRule.class)); FlowRuleManager.register2Property(flowRuleEtcdDataSource.getProperty()); List rules = FlowRuleManager.getRules(); System.out.println(rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-jax-rs 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-jax-rs-adapter ${project.version} org.jboss.resteasy resteasy-spring-boot-starter 4.5.1.Final org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-test ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/CustomExceptionMapper.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; import org.springframework.stereotype.Component; import javax.annotation.Priority; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * @author sea */ @Component @Provider @Priority(javax.ws.rs.Priorities.USER - 1) public class CustomExceptionMapper implements ExceptionMapper { @Override public Response toResponse(Throwable exception) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Unknown Server Error") .build(); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloEntity.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; /** * @author sea */ public class HelloEntity { Long id; String msg; public HelloEntity() { } public HelloEntity(String msg) { this.msg = msg; } public HelloEntity(Long id, String msg) { this.id = id; this.msg = msg; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @Override public String toString() { return "HelloEntity{" + "id=" + id + ", msg='" + msg + '\'' + '}'; } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/HelloResource.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; import org.springframework.stereotype.Component; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * HelloResource * @author sea */ @Path("/hello") @Produces(MediaType.APPLICATION_JSON) @Component public class HelloResource { @GET public HelloEntity sayHello() { return new HelloEntity("hello"); } @GET @Path("/{id}") public HelloEntity get(@PathParam(value = "id") Long id) { return new HelloEntity(id, "hello"); } @GET @Path("/list") public List getAll() { return IntStream.rangeClosed(1, 1000) .mapToObj(i -> new HelloEntity((long)i, "hello")) .collect(Collectors.toList()); } @Path("/ex") @GET @Produces({ MediaType.APPLICATION_JSON }) public String exception() { throw new RuntimeException("test exception mapper"); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsClientDemo.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; import com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsClientTemplate; import com.alibaba.csp.sentinel.util.function.Supplier; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Response; import java.util.concurrent.TimeUnit; /** * @author sea */ public class JaxRsClientDemo { public static void main(String[] args) { Client client = ClientBuilder.newBuilder() .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(3, TimeUnit.SECONDS) .build(); final String host = "http://127.0.0.1:8181"; final String url = "/hello/1"; String resourceName = "GET:" + url; Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { @Override public Response get() { return client.target(host).path(url).request() .get(); } }); System.out.println(response.readEntity(HelloEntity.class)); System.exit(0); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/JaxRsDemoApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *

          Add the JVM parameter to connect to the dashboard:

          * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-jax-rs} * * @author sea */ @SpringBootApplication public class JaxRsDemoApplication { public static void main(String[] args) { SpringApplication.run(JaxRsDemoApplication.class); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/java/com/alibaba/csp/sentinel/demo/jaxrs/SentinelJaxRsConfig.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.jaxrs; import com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsProviderFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author sea */ @Configuration(proxyBeanMethods = false) public class SentinelJaxRsConfig { @Bean public SentinelJaxRsProviderFilter sentinelJaxRsProviderFilter() { return new SentinelJaxRsProviderFilter(); } } ================================================ FILE: sentinel-demo/sentinel-demo-jax-rs/src/main/resources/application.properties ================================================ server.port=8181 ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-log-logback com.alibaba.csp sentinel-transport-common ch.qos.logback logback-classic 1.2.3 ch.qos.logback logback-core 1.2.3 junit junit test com.github.stefanbirkner system-rules RELEASE test ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/main/java/com/alibaba/csp/sentinel/demo/log/logback/CommandCenterLogLoggerImpl.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.log.logback; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.log.LogTarget; import com.alibaba.csp.sentinel.log.Logger; import org.slf4j.LoggerFactory; /** * This class is a demo shows how to create a customized logger implementation. * *
            *
          • 1. Create a class which implements the {@link Logger} SPI interface
          • *
          • 2. Use a {@link LogTarget} to specify the log type
          • *
          • 3. Implement your own method
          • *
          • 4. Add your logger in {@code com.alibaba.csp.sentinel.log.Logger} file which is stored in * {@code resources/META-INF/services/} directory
          • *
          * * @author xue8 */ @LogTarget(value = CommandCenterLog.LOGGER_NAME) public class CommandCenterLogLoggerImpl implements Logger { private final org.slf4j.Logger logger = LoggerFactory.getLogger(CommandCenterLog.LOGGER_NAME); @Override public void info(String format, Object... arguments) { logger.info(format, arguments); } @Override public void info(String msg, Throwable e) { logger.info(msg, e); } @Override public void warn(String format, Object... arguments) { logger.warn(format, arguments); } @Override public void warn(String msg, Throwable e) { logger.warn(msg, e); } @Override public void trace(String format, Object... arguments) { logger.trace(format, arguments); } @Override public void trace(String msg, Throwable e) { logger.trace(msg, e); } @Override public void debug(String format, Object... arguments) { logger.debug(format, arguments); } @Override public void debug(String msg, Throwable e) { logger.debug(msg, e); } @Override public void error(String format, Object... arguments) { logger.error(format, arguments); } @Override public void error(String msg, Throwable e) { logger.error(msg, e); } } ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/main/java/com/alibaba/csp/sentinel/demo/log/logback/RecordLogLoggerImpl.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.log.logback; import com.alibaba.csp.sentinel.log.LogTarget; import com.alibaba.csp.sentinel.log.Logger; import com.alibaba.csp.sentinel.log.RecordLog; import org.slf4j.LoggerFactory; /** * This class is a demo shows how to create a customized logger implementation. * *
            *
          • 1. Create a class which implements the {@link Logger} SPI interface
          • *
          • 2. Use a {@link LogTarget} to specify the log type
          • *
          • 3. Implement your own method
          • *
          • 4. Add your logger in {@code com.alibaba.csp.sentinel.log.Logger} file which is stored in * {@code resources/META-INF/services/} directory
          • *
          * * @author xue8 */ @LogTarget(value = RecordLog.LOGGER_NAME) public class RecordLogLoggerImpl implements Logger { private final org.slf4j.Logger logger = LoggerFactory.getLogger(RecordLog.LOGGER_NAME); @Override public void info(String format, Object... arguments) { logger.info(format, arguments); } @Override public void info(String msg, Throwable e) { logger.info(msg, e); } @Override public void warn(String format, Object... arguments) { logger.warn(format, arguments); } @Override public void warn(String msg, Throwable e) { logger.warn(msg, e); } @Override public void trace(String format, Object... arguments) { logger.trace(format, arguments); } @Override public void trace(String msg, Throwable e) { logger.trace(msg, e); } @Override public void debug(String format, Object... arguments) { logger.debug(format, arguments); } @Override public void debug(String msg, Throwable e) { logger.debug(msg, e); } @Override public void error(String format, Object... arguments) { logger.error(format, arguments); } @Override public void error(String msg, Throwable e) { logger.error(msg, e); } } ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.log.Logger ================================================ com.alibaba.csp.sentinel.demo.log.logback.RecordLogLoggerImpl com.alibaba.csp.sentinel.demo.log.logback.CommandCenterLogLoggerImpl ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/main/resources/logback.xml ================================================ %-5level %logger - %msg%n sentinel-record.log true %-5level %logger - %msg%n sentinel-command-center.log true %-5level %logger - %msg%n ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/test/java/com/alibaba/csp/sentinel/demo/log/logback/CommandCenterLogTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.log.logback; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.SystemOutRule; /** * @author xue8 */ public class CommandCenterLogTest { @Rule public SystemOutRule log = new SystemOutRule().enableLog(); @Test public void testLog() { CommandCenterLog.info("init"); log.clearLog(); int count = 0; // info test while (count++ < 1000) { log.clearLog(); CommandCenterLog.info("Count {}", count); String str = String.format("INFO sentinelCommandCenterLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // warn test while (count++ < 2000) { log.clearLog(); CommandCenterLog.warn("Count {}", count); String str = String.format("WARN sentinelCommandCenterLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // trace test while (count++ < 3000) { log.clearLog(); CommandCenterLog.trace("Count {}", count); String str = String.format("TRACE sentinelCommandCenterLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // debug test while (count++ < 4000) { log.clearLog(); CommandCenterLog.debug("Count {}", count); String str = String.format("DEBUG sentinelCommandCenterLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // test error while (count++ < 5000) { log.clearLog(); CommandCenterLog.error("Count {}", count); String str = String.format("ERROR sentinelCommandCenterLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } } @Test public void testLogException() { CommandCenterLog.info("init"); log.clearLog(); Exception e = new Exception("ex"); // info test CommandCenterLog.info("Error", e); // split the log for test String[] logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("INFO sentinelCommandCenterLogger - Error", logSplit[0]); // warn test log.clearLog(); CommandCenterLog.warn("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("WARN sentinelCommandCenterLogger - Error", logSplit[0]); // trace test log.clearLog(); CommandCenterLog.trace("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("TRACE sentinelCommandCenterLogger - Error", logSplit[0]); // debug test log.clearLog(); CommandCenterLog.debug("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("DEBUG sentinelCommandCenterLogger - Error", logSplit[0]); // error test log.clearLog(); CommandCenterLog.error("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("ERROR sentinelCommandCenterLogger - Error", logSplit[0]); } } ================================================ FILE: sentinel-demo/sentinel-demo-log-logback/src/test/java/com/alibaba/csp/sentinel/demo/log/logback/RecordLogTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.log.logback; import com.alibaba.csp.sentinel.log.RecordLog; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.SystemOutRule; /** * @author xue8 */ public class RecordLogTest { @Rule public SystemOutRule log = new SystemOutRule().enableLog(); @Test public void testLog() { RecordLog.info("init"); log.clearLog(); int count = 0; // info test while (count++ < 1000) { log.clearLog(); RecordLog.info("Count {}", count); String str = String.format("INFO sentinelRecordLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // warn test while (count++ < 2000) { log.clearLog(); RecordLog.warn("Count {}", count); String str = String.format("WARN sentinelRecordLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // trace test while (count++ < 3000) { log.clearLog(); RecordLog.trace("Count {}", count); String str = String.format("TRACE sentinelRecordLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // debug test while (count++ < 4000) { log.clearLog(); RecordLog.debug("Count {}", count); String str = String.format("DEBUG sentinelRecordLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } // test error while (count++ < 5000) { log.clearLog(); RecordLog.error("Count {}", count); String str = String.format("ERROR sentinelRecordLogger - Count %d" + System.lineSeparator(), count); Assert.assertEquals(str, log.getLog()); } } @Test public void testLogException() { RecordLog.info("init"); log.clearLog(); Exception e = new Exception("ex"); // info test RecordLog.info("Error", e); // split the log for test String[] logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("INFO sentinelRecordLogger - Error", logSplit[0]); // warn test log.clearLog(); RecordLog.warn("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("WARN sentinelRecordLogger - Error", logSplit[0]); // trace test log.clearLog(); RecordLog.trace("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("TRACE sentinelRecordLogger - Error", logSplit[0]); // debug test log.clearLog(); RecordLog.debug("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("DEBUG sentinelRecordLogger - Error", logSplit[0]); // error test log.clearLog(); RecordLog.error("Error", e); logSplit = log.getLog().split(System.lineSeparator()); Assert.assertEquals("ERROR sentinelRecordLogger - Error", logSplit[0]); } } ================================================ FILE: sentinel-demo/sentinel-demo-motan/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-motan 1.1.8 1.7.28 com.alibaba.csp sentinel-motan-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ${project.version} com.weibo motan-core ${motan.version} compile com.weibo motan-transport-netty4 ${motan.version} org.slf4j slf4j-api ${slf4j.version} org.slf4j slf4j-log4j12 ${slf4j.version} test ================================================ FILE: sentinel-demo/sentinel-demo-motan/src/main/java/com/alibaba/csp/sentinel/demo/motan/SentinelMotanConsumerService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.motan; import com.alibaba.csp.sentinel.demo.motan.service.MotanDemoService; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.weibo.api.motan.config.ProtocolConfig; import com.weibo.api.motan.config.RefererConfig; import com.weibo.api.motan.config.RegistryConfig; import java.util.ArrayList; import java.util.List; /** * @author zhangxn8 */ public class SentinelMotanConsumerService { private static final String INTERFACE_RES_KEY = MotanDemoService.class.getName(); private static final String RES_KEY = INTERFACE_RES_KEY + ":hello(java.lang.String)"; public static void main(String[] args) { RefererConfig motanDemoServiceReferer = new RefererConfig(); // 设置接口及实现类 motanDemoServiceReferer.setInterface(MotanDemoService.class); // 配置服务的group以及版本号 motanDemoServiceReferer.setGroup("motan-demo-rpc"); motanDemoServiceReferer.setVersion("1.0"); motanDemoServiceReferer.setRequestTimeout(100000); // 配置注册中心直连调用 RegistryConfig registry = new RegistryConfig(); //use direct registry registry.setRegProtocol("direct"); registry.setAddress("127.0.0.1:8002"); // use ZooKeeper: 2181 or consul:8500 registry // registry.setRegProtocol("consul"); // registry.setAddress("127.0.0.1:8500"); motanDemoServiceReferer.setRegistry(registry); // 配置RPC协议 ProtocolConfig protocol = new ProtocolConfig(); protocol.setId("motan"); protocol.setName("motan"); motanDemoServiceReferer.setProtocol(protocol); motanDemoServiceReferer.setDirectUrl("localhost:8002"); // 注册中心直连调用需添加此配置 initFlowRule(5, false); // 使用服务 MotanDemoService service = motanDemoServiceReferer.getRef(); for (int i = 0 ;i< 5000 ;i++) { System.out.println(service.hello("motan")); } System.exit(0); } private static void initFlowRule(int interfaceFlowLimit, boolean method) { FlowRule flowRule = new FlowRule(INTERFACE_RES_KEY) .setCount(interfaceFlowLimit) .setGrade(RuleConstant.FLOW_GRADE_QPS); List list = new ArrayList<>(); if (method) { FlowRule flowRule1 = new FlowRule(RES_KEY) .setCount(5) .setGrade(RuleConstant.FLOW_GRADE_QPS); list.add(flowRule1); } list.add(flowRule); FlowRuleManager.loadRules(list); } } ================================================ FILE: sentinel-demo/sentinel-demo-motan/src/main/java/com/alibaba/csp/sentinel/demo/motan/SentinelMotanProviderService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.motan; import com.alibaba.csp.sentinel.demo.motan.service.MotanDemoService; import com.alibaba.csp.sentinel.demo.motan.service.impl.MotanDemoServiceImpl; import com.alibaba.csp.sentinel.init.InitExecutor; import com.weibo.api.motan.common.MotanConstants; import com.weibo.api.motan.config.ProtocolConfig; import com.weibo.api.motan.config.RegistryConfig; import com.weibo.api.motan.config.ServiceConfig; import com.weibo.api.motan.util.MotanSwitcherUtil; /** * @author zhangxn8 */ public class SentinelMotanProviderService { public static void main(String[] args) { InitExecutor.doInit(); ServiceConfig motanDemoService = new ServiceConfig(); // 设置接口及实现类 motanDemoService.setInterface(MotanDemoService.class); motanDemoService.setRef(new MotanDemoServiceImpl()); // 配置服务的group以及版本号 motanDemoService.setGroup("motan-demo-rpc"); motanDemoService.setVersion("1.0"); // 配置注册中心直连调用 RegistryConfig registry = new RegistryConfig(); //use local registry registry.setRegProtocol("local"); // use ZooKeeper: 2181 or consul:8500 registry // registry.setRegProtocol("consul"); // registry.setAddress("127.0.0.1:8500"); // registry.setCheck("false"); //是否检查是否注册成功 motanDemoService.setRegistry(registry); // 配置RPC协议 ProtocolConfig protocol = new ProtocolConfig(); protocol.setId("motan"); protocol.setName("motan"); motanDemoService.setProtocol(protocol); motanDemoService.setExport("motan:8002"); motanDemoService.export(); MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER, true); System.out.println("server start..."); } } ================================================ FILE: sentinel-demo/sentinel-demo-motan/src/main/java/com/alibaba/csp/sentinel/demo/motan/service/MotanDemoService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.motan.service; /** * @author zhangxn8 */ public interface MotanDemoService { String hello(String name); } ================================================ FILE: sentinel-demo/sentinel-demo-motan/src/main/java/com/alibaba/csp/sentinel/demo/motan/service/impl/MotanDemoServiceImpl.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.motan.service.impl; import com.alibaba.csp.sentinel.demo.motan.service.MotanDemoService; /** * @author zhangxn8 */ public class MotanDemoServiceImpl implements MotanDemoService { @Override public String hello(String name) { System.out.println(name); return "Hello " + name + "!"; } } ================================================ FILE: sentinel-demo/sentinel-demo-motan/src/main/resources/sentinel.properties ================================================ csp.sentinel.dashboard.server=localhost:8080 ================================================ FILE: sentinel-demo/sentinel-demo-nacos-datasource/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-nacos-datasource com.alibaba.csp sentinel-core com.alibaba.csp sentinel-datasource-extension com.alibaba.csp sentinel-datasource-nacos com.alibaba fastjson ch.qos.logback logback-classic 1.2.11 ================================================ FILE: sentinel-demo/sentinel-demo-nacos-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/nacos/FlowQpsRunner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.nacos; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Flow QPS runner. * * @author Carpenter Lee * @author Eric Zhao */ class FlowQpsRunner { private final String resourceName; private final int threadCount; private int seconds; public FlowQpsRunner(String resourceName, int threadCount, int seconds) { this.resourceName = resourceName; this.threadCount = threadCount; this.seconds = seconds; } private final AtomicInteger pass = new AtomicInteger(); private final AtomicInteger block = new AtomicInteger(); private final AtomicInteger total = new AtomicInteger(); private volatile boolean stop = false; public void simulateTraffic() { for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("simulate-traffic-Task"); t.start(); } } public void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } final class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; try { entry = SphU.entry(resourceName); // token acquired, means pass pass.addAndGet(1); } catch (BlockException e1) { block.incrementAndGet(); } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } Random random2 = new Random(); try { TimeUnit.MILLISECONDS.sleep(random2.nextInt(50)); } catch (InterruptedException e) { // ignore } } } } final class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + " send qps is: " + oneSecondTotal); System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("time cost: " + cost + " ms"); System.out.println("total:" + total.get() + ", pass:" + pass.get() + ", block:" + block.get()); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-nacos-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/nacos/NacosConfigSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.nacos; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; /** * Nacos config sender for demo. * * @author Eric Zhao */ public class NacosConfigSender { public static void main(String[] args) throws Exception { final String remoteAddress = "localhost:8848"; final String groupId = "Sentinel_Demo"; final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule"; final String rule = "[\n" + " {\n" + " \"resource\": \"TestResource\",\n" + " \"controlBehavior\": 0,\n" + " \"count\": 5.0,\n" + " \"grade\": 1,\n" + " \"limitApp\": \"default\",\n" + " \"strategy\": 0\n" + " }\n" + "]"; ConfigService configService = NacosFactory.createConfigService(remoteAddress); System.out.println(configService.publishConfig(dataId, groupId, rule)); } } ================================================ FILE: sentinel-demo/sentinel-demo-nacos-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/nacos/NacosDataSourceDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.datasource.nacos; import java.util.List; import java.util.Properties; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.alibaba.nacos.api.PropertyKeyConst; /** * This demo demonstrates how to use Nacos as the data source of Sentinel rules. * Before you start, you need to start a Nacos server in local first, and then * use {@link NacosConfigSender} to publish initial rule configuration to Nacos. * * @author Eric Zhao */ public class NacosDataSourceDemo { private static final String KEY = "TestResource"; // nacos server ip private static final String remoteAddress = "localhost:8848"; // nacos group private static final String groupId = "Sentinel_Demo"; // nacos dataId private static final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule"; // if change to true, should be config NACOS_NAMESPACE_ID private static boolean isDemoNamespace = false; // fill your namespace id,if you want to use namespace. for example: 0f5c7314-4983-4022-ad5a-347de1d1057d,you can get it on nacos's console private static final String NACOS_NAMESPACE_ID = "${namespace}"; public static void main(String[] args) { if (isDemoNamespace) { loadMyNamespaceRules(); } else { loadRules(); } // Assume we config: resource is `TestResource`, initial QPS threshold is 5. FlowQpsRunner runner = new FlowQpsRunner(KEY, 1, 100); runner.simulateTraffic(); runner.tick(); } private static void loadRules() { ReadableDataSource> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, source -> JSON.parseObject(source, new TypeReference>() { })); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } private static void loadMyNamespaceRules() { Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, remoteAddress); properties.put(PropertyKeyConst.NAMESPACE, NACOS_NAMESPACE_ID); ReadableDataSource> flowRuleDataSource = new NacosDataSource<>(properties, groupId, dataId, source -> JSON.parseObject(source, new TypeReference>() { })); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } } ================================================ FILE: sentinel-demo/sentinel-demo-okhttp/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-okhttp 2.5.12 3.6.0 com.alibaba.csp sentinel-okhttp-adapter ${project.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-test ${spring.boot.version} com.squareup.okhttp3 okhttp ${okhttp.version} com.alibaba.csp sentinel-transport-simple-http ================================================ FILE: sentinel-demo/sentinel-demo-okhttp/src/main/java/com/alibaba/csp/sentinel/demo/okhttp/OkHttpDemoApplication.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.okhttp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author zhaoyuguang */ @SpringBootApplication public class OkHttpDemoApplication { public static void main(String[] args) { SpringApplication.run(OkHttpDemoApplication.class); } } ================================================ FILE: sentinel-demo/sentinel-demo-okhttp/src/main/java/com/alibaba/csp/sentinel/demo/okhttp/controller/OkHttpTestController.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.okhttp.controller; import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpConfig; import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpInterceptor; import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /** * @author zhaoyuguang */ @RestController public class OkHttpTestController { @Value("${server.port}") private Integer port; private final OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SentinelOkHttpInterceptor(new SentinelOkHttpConfig((request, connection) -> { String regex = "/okhttp/back/"; String url = request.url().toString(); if (url.contains(regex)) { url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; } return request.method() + ":" + url; }, new DefaultOkHttpFallback()))) .build(); @RequestMapping("/okhttp/back") public String back() { return "Welcome Back!"; } @RequestMapping("/okhttp/back/{id}") public String back(@PathVariable String id) { return "Welcome Back! " + id; } @RequestMapping("/okhttp/testcase/{id}") public String testcase(@PathVariable String id) throws Exception { return getRemoteString(id); } @RequestMapping("/okhttp/testcase") public String testcase() throws Exception { return getRemoteString(null); } private String getRemoteString(String id) throws IOException { Request request = new Request.Builder() .url("http://localhost:" + port + "/okhttp/back" + (id == null ? "" : "/" + id)) .build(); Response response = client.newCall(request).execute(); return response.body().string(); } } ================================================ FILE: sentinel-demo/sentinel-demo-okhttp/src/main/resources/application.properties ================================================ spring.application.name=sentinel-demo-okhttp server.port=8085 ================================================ FILE: sentinel-demo/sentinel-demo-parameter-flow-control/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-parameter-flow-control com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-transport-simple-http ================================================ FILE: sentinel-demo/sentinel-demo-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/demo/flow/param/ParamFlowQpsDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow.param; import java.util.Collections; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; /** * This demo demonstrates flow control by frequent ("hot spot") parameters. * * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowQpsDemo { private static final int PARAM_A = 1; private static final int PARAM_B = 2; private static final int PARAM_C = 3; private static final int PARAM_D = 4; /** * Here we prepare different parameters to validate flow control by parameters. */ private static final Integer[] PARAMS = new Integer[] {PARAM_A, PARAM_B, PARAM_C, PARAM_D}; private static final String RESOURCE_KEY = "resA"; public static void main(String[] args) throws Exception { initParamFlowRules(); final int threadCount = 20; ParamFlowQpsRunner runner = new ParamFlowQpsRunner<>(PARAMS, RESOURCE_KEY, threadCount, 120); runner.tick(); Thread.sleep(1000); runner.simulateTraffic(); } private static void initParamFlowRules() { // QPS mode, threshold is 5 for every frequent "hot spot" parameter in index 0 (the first arg). ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY) .setParamIdx(0) .setGrade(RuleConstant.FLOW_GRADE_QPS) //.setDurationInSec(3) //.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) //.setMaxQueueingTimeMs(600) .setCount(5); // We can set threshold count for specific parameter value individually. // Here we add an exception item. That means: QPS threshold of entries with parameter `PARAM_B` (type: int) // in index 0 will be 10, rather than the global threshold (5). ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)) .setClassType(int.class.getName()) .setCount(10); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } } ================================================ FILE: sentinel-demo/sentinel-demo-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/demo/flow/param/ParamFlowQpsRunner.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.flow.param; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * A traffic runner to simulate flow for different parameters. * * @author Eric Zhao * @since 0.2.0 */ class ParamFlowQpsRunner { private final T[] params; private final String resourceName; private int seconds; private final int threadCount; private final Map passCountMap = new ConcurrentHashMap<>(); private final Map blockCountMap = new ConcurrentHashMap<>(); private volatile boolean stop = false; public ParamFlowQpsRunner(T[] params, String resourceName, int threadCount, int seconds) { assertTrue(params != null && params.length > 0, "Parameter array should not be empty"); assertTrue(StringUtil.isNotBlank(resourceName), "Resource name cannot be empty"); assertTrue(seconds > 0, "Time period should be positive"); assertTrue(threadCount > 0 && threadCount <= 1000, "Invalid thread count"); this.params = params; this.resourceName = resourceName; this.seconds = seconds; this.threadCount = threadCount; for (T param : params) { assertTrue(param != null, "Parameters should not be null"); passCountMap.putIfAbsent(param, new AtomicLong()); blockCountMap.putIfAbsent(param, new AtomicLong()); } } private void assertTrue(boolean b, String message) { if (!b) { throw new IllegalArgumentException(message); } } /** * Pick one of provided parameters randomly. * * @return picked parameter */ private T generateParam() { int i = ThreadLocalRandom.current().nextInt(0, params.length); return params[i]; } void simulateTraffic() { for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new RunTask()); t.setName("sentinel-simulate-traffic-task-" + i); t.start(); } } void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } private void passFor(T param) { passCountMap.get(param).incrementAndGet(); // System.out.println(String.format("Parameter <%s> passed at: %d", param, TimeUtil.currentTimeMillis())); } private void blockFor(T param) { blockCountMap.get(param).incrementAndGet(); } final class RunTask implements Runnable { @Override public void run() { while (!stop) { Entry entry = null; T param = generateParam(); try { entry = SphU.entry(resourceName, EntryType.IN, 1, param); // Add pass for parameter. passFor(param); } catch (BlockException e) { // block.incrementAndGet(); blockFor(param); } catch (Exception ex) { // biz exception ex.printStackTrace(); } finally { // total.incrementAndGet(); if (entry != null) { entry.exit(1, param); } } sleep(ThreadLocalRandom.current().nextInt(0, 10)); } } } private void sleep(int timeMs) { try { TimeUnit.MILLISECONDS.sleep(timeMs); } catch (InterruptedException e) { // ignore } } final class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("Begin to run! Go go go!"); System.out.println("See corresponding metrics.log for accurate statistic data"); Map map = new HashMap<>(params.length); for (T param : params) { map.putIfAbsent(param, 0L); } while (!stop) { sleep(1000); // There may be a mismatch for time window of internal sliding window. // See corresponding `metrics.log` for accurate statistic log. for (T param : params) { System.out.println(String.format( "[%d][%d] Parameter flow metrics for resource %s: " + "pass count for param <%s> is %d, block count: %d", seconds, TimeUtil.currentTimeMillis(), resourceName, param, passCountMap.get(param).getAndSet(0), blockCountMap.get(param).getAndSet(0))); } System.out.println("============================="); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("Time cost: " + cost + " ms"); System.exit(0); } } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/.dockerignore ================================================ * !target/*-runner !target/*-runner.jar !target/lib/* ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/.gitignore ================================================ # Eclipse .project .classpath .settings/ bin/ # IntelliJ .idea *.ipr *.iml *.iws # NetBeans nb-configuration.xml # Visual Studio Code .vscode # OSX .DS_Store # Vim *.swp *.swo # patch *.orig *.rej # Maven target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup release.properties ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/.mvn/wrapper/MavenWrapperDownloader.java ================================================ /* * Copyright 2007-present the original author or 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. */ import java.net.*; import java.io.*; import java.nio.channels.*; import java.util.Properties; public class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "0.5.6"; /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to * use instead of the default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main(String args[]) { System.out.println("- Downloader started"); File baseDirectory = new File(args[0]); System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; if(mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); } catch (IOException e) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { if(mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { // Ignore ... } } } System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); if(!outputFile.getParentFile().exists()) { if(!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } } System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { downloadFileFromURL(url, outputFile); System.out.println("Done"); System.exit(0); } catch (Throwable e) { System.out.println("- Error downloading"); e.printStackTrace(); System.exit(1); } } private static void downloadFileFromURL(String urlString, File destination) throws Exception { if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { String username = System.getenv("MVNW_USERNAME"); char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } URL website = new URL(urlString); ReadableByteChannel rbc; rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(destination); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/.mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/README.md ================================================ # sentinel-demo-quarkus project This project uses Quarkus, the Supersonic Subatomic Java Framework. If you want to learn more about Quarkus, please visit its website: [quarkus.io](https://quarkus.io/) ## Running the application in dev mode You can run your application in dev mode that enables live coding using: ```bash ./mvnw quarkus:dev ``` ## Packaging and running the application The application can be packaged using `./mvnw package`. It produces the `sentinel-demo-quarkus-1.0-SNAPSHOT-runner.jar` file in the `/target` directory. Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. The application is now runnable using `java -jar target/sentinel-demo-quarkus-1.0-SNAPSHOT-runner.jar`. ## Creating a native executable You can create a native executable using: `./mvnw package -Pnative`. Or, if you don't have GraalVM installed, you can run the native executable build in a container using: `./mvnw package -Pnative -Dquarkus.native.container-build=true`. You can then execute your native executable with: `./target/sentinel-demo-quarkus-1.0-SNAPSHOT-runner` If you want to learn more about building native executables, please consult [building-native-image](https://quarkus.io/guides/building-native-image) ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/build-native.sh ================================================ #!/usr/bin/env bash ./mvnw package -X -Pnative \ -Dquarkus.native.graalvm-home=/opt/graalvm-ce-java8-20.0.0 #-Dquarkus.native.container-build=true \ #-Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:20.0.0-java11 #sudo docker build -f src/main/docker/Dockerfile.native -t quarkus/sentinel-demo-quarkus . #sudo docker run -i --rm -p 8080:8182 quarkus/sentinel-demo-quarkus ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/build.sh ================================================ #!/usr/bin/env bash mvn package sudo docker build -f src/main/docker/Dockerfile.jvm -t quarkus/sentinel-demo-quarkus-jvm . sudo docker run -i --rm -p 8080:8182 quarkus/sentinel-demo-quarkus-jvm ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # 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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-quarkus 3.8.1 true 1.8 1.8 UTF-8 UTF-8 1.4.1.Final quarkus-universe-bom io.quarkus 1.4.1.Final 2.22.1 ${quarkus.platform.group-id} ${quarkus.platform.artifact-id} ${quarkus.platform.version} pom import io.quarkus quarkus-resteasy io.quarkus quarkus-jackson com.alibaba.csp sentinel-core com.alibaba.csp sentinel-logging-slf4j ${project.version} com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-jax-rs-quarkus-adapter ${project.version} com.alibaba.csp sentinel-annotation-quarkus-adapter ${project.version} com.alibaba.csp sentinel-native-image-quarkus-adapter ${project.version} io.quarkus quarkus-junit5 test io.rest-assured rest-assured test io.quarkus quarkus-maven-plugin ${quarkus-plugin.version} build maven-compiler-plugin ${compiler-plugin.version} maven-surefire-plugin ${surefire-plugin.version} org.jboss.logmanager.LogManager org.jboss.jandex jandex-maven-plugin 1.0.7 make-index jandex native native maven-failsafe-plugin ${surefire-plugin.version} integration-test verify ${project.build.directory}/${project.build.finalName}-runner native true -J-Dcsp.sentinel.log.dir=/tmp,-J-Dcsp.sentinel.api.port=8722,-J-Dcsp.sentinel.heartbeat.client.ip=127.0.0.1,-J-Dcsp.sentinel.dashboard.server=127.0.0.1:8080,-J-Dproject.name=sentinel-demo-quarkus,-H:+TraceClassInitialization,--report-unsupported-elements-at-runtime,--allow-incomplete-classpath ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/docker/Dockerfile.jvm ================================================ #### # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode # # Before building the docker image run: # # mvn package # # Then, build the image with: # # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/sentinel-demo-quarkus-jvm . # # Then run the container using: # # docker run -i --rm -p 8080:8182 quarkus/sentinel-demo-quarkus-jvm # # If you want to include the debug port into your docker image # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 # # Then run the container using : # # docker run -i --rm -p 8080:8182 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/sentinel-demo-quarkus-jvm # ### FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 ARG JAVA_PACKAGE=java-1.8.0-openjdk-headless #ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.5 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' # Install java and the run-java script # Also set up permissions for user `1001` RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && microdnf update \ && microdnf clean all \ && mkdir /deployments \ && chown 1001 /deployments \ && chmod "g+rwX" /deployments \ && chown 1001:root /deployments \ && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ && chown 1001 /deployments/run-java.sh \ && chmod 540 /deployments/run-java.sh \ && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" COPY target/lib/* /deployments/lib/ COPY target/*-runner.jar /deployments/app.jar EXPOSE 8182 USER 1001 ENTRYPOINT [ "/deployments/run-java.sh" ] ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/docker/Dockerfile.native ================================================ #### # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode # # Before building the docker image run: # # mvn package -Pnative -Dquarkus.native.container-build=true # # Then, build the image with: # # docker build -f src/main/docker/Dockerfile.native -t quarkus/sentinel-demo-quarkus . # # Then run the container using: # # docker run -i --rm -p 8080:8182 quarkus/sentinel-demo-quarkus # ### FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 WORKDIR /work/ COPY target/*-runner /work/application # set up permissions for user `1001` RUN chmod 775 /work /work/application \ && chown -R 1001 /work \ && chmod -R "g+rwX" /work \ && chown -R 1001:root /work EXPOSE 8182 USER 1001 CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/java/com/alibaba/csp/sentinel/demo/quarkus/AppLifecycleBean.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import io.quarkus.runtime.StartupEvent; import org.jboss.logging.Logger; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import java.util.Arrays; /** * @author sea */ @ApplicationScoped public class AppLifecycleBean { private static final Logger LOGGER = Logger.getLogger("ListenerBean"); void onStart(@Observes StartupEvent ev) { LOGGER.info("The application is starting..."); // Only for test here. Actually it's recommended to configure rules via data-source. FlowRule rule1 = new FlowRule() .setCount(1) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setResource("GET:/hello/txt") .setLimitApp("default") .as(FlowRule.class); FlowRule rule2 = new FlowRule("greeting2") .setCount(1) .setGrade(RuleConstant.FLOW_GRADE_QPS) .as(FlowRule.class); FlowRuleManager.loadRules(Arrays.asList(rule1, rule2)); DegradeRule degradeRule1 = new DegradeRule("greeting1") .setCount(1) .setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType()) .setTimeWindow(5) .setStatIntervalMs(10000) .setMinRequestAmount(1); DegradeRuleManager.loadRules(Arrays.asList(degradeRule1)); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/java/com/alibaba/csp/sentinel/demo/quarkus/CustomExceptionMapper.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import org.jboss.logging.Logger; import javax.annotation.Priority; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * @author sea */ //@Provider //@Priority(-javax.ws.rs.Priorities.USER) public class CustomExceptionMapper implements ExceptionMapper { static final Logger log = Logger.getLogger(CustomExceptionMapper.class); @Override public Response toResponse(Throwable exception) { log.error(exception.getMessage(), exception); return Response.serverError().entity(exception.getMessage()).build(); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/java/com/alibaba/csp/sentinel/demo/quarkus/GreetingFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import com.alibaba.csp.sentinel.slots.block.BlockException; import io.quarkus.runtime.annotations.RegisterForReflection; /** * @author sea */ @RegisterForReflection public class GreetingFallback { public static String globalBlockHandler(String name, BlockException ex) { return "globalBlockHandler, ex:" + ex.getMessage(); } public static String globalDefaultFallback(String name, Throwable t) { return "globalDefaultFallback, ex:" + t.getMessage(); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/java/com/alibaba/csp/sentinel/demo/quarkus/GreetingResource.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.concurrent.*; @Path("/hello") public class GreetingResource { @Inject GreetingService greetingService; ExecutorService executor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); @GET @Path("/txt") @Produces(MediaType.TEXT_PLAIN) public String hello() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(300); return "hello"; } @GET @Path("/fallback/{name}") @Produces(MediaType.TEXT_PLAIN) public String fallback(@PathParam(value = "name") String name) { return greetingService.greeting(name); } @GET @Path("/fallback2/{name}") @Produces(MediaType.TEXT_PLAIN) public String fallback2(@PathParam(value = "name") String name) { return greetingService.greetingWithFallbackName(name); } @Path("/async") @GET @Produces({ MediaType.APPLICATION_JSON }) public void asyncHello(@Suspended final AsyncResponse asyncResponse) { executor.submit(() -> { try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } asyncResponse.resume("Hello!"); }); } @Path("/ex") @GET @Produces({ MediaType.APPLICATION_JSON }) public String exception() { throw new RuntimeException("test exception mapper"); } @Path("/400") @GET @Produces({ MediaType.APPLICATION_JSON }) public String badRequest() { throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity("test return 400") .build()); } @Path("/delay/{seconds}") @GET @Produces({ MediaType.APPLICATION_JSON }) public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException { TimeUnit.SECONDS.sleep(seconds); return "finish"; } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/java/com/alibaba/csp/sentinel/demo/quarkus/GreetingService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding; import io.quarkus.runtime.annotations.RegisterForReflection; import javax.enterprise.context.ApplicationScoped; /** * @author sea */ @ApplicationScoped @RegisterForReflection public class GreetingService { @SentinelResourceBinding(value = "greeting1", fallback = "globalDefaultFallback", fallbackClass = GreetingFallback.class, blockHandler = "globalBlockHandler", blockHandlerClass = GreetingFallback.class) public String greeting(String name) { if ("degrade".equals(name)) { throw new RuntimeException("test sentinel fallback"); } return "hello " + name; } @SentinelResourceBinding(value = "greeting2", fallback = "greetingFallback") public String greetingWithFallbackName(String name) { if ("degrade".equals(name)) { throw new RuntimeException("test sentinel fallback"); } return "hello " + name; } public String greetingFallback(String name, Throwable t) { return "greetingFallback: " + t.getClass().getSimpleName(); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/resources/META-INF/resources/index.html ================================================ sentinel-quarkus-demo - 1.0-SNAPSHOT

          Congratulations, you have created a new Quarkus application.

          Why do you see this?

          This page is served by Quarkus. The source is in src/main/resources/META-INF/resources/index.html.

          What can I do from here?

          If not already done, run the application in dev mode using: mvn compile quarkus:dev.

          • Add REST resources, Servlets, functions and other services in src/main/java.
          • Your static assets are located in src/main/resources/META-INF/resources.
          • Configure your application in src/main/resources/application.properties.

          How do I get rid of this page?

          Just delete the src/main/resources/META-INF/resources/index.html file.

          Application

          • GroupId: com.alibaba.csp
          • ArtifactId: sentinel-quarkus-demo
          • Version: 1.0-SNAPSHOT
          • Quarkus Version: 1.4.1.Final
          ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/main/resources/application.properties ================================================ # Configuration file # key = value quarkus.http.port=8182 ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/test/java/com/alibaba/csp/sentinel/demo/quarkus/GreetingResourceTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; @QuarkusTest public class GreetingResourceTest { @AfterEach public void cleanUp() { ClusterBuilderSlot.resetClusterNodes(); } @Test public void testSentinelJaxRsQuarkusAdapter() { given() .when().get("/hello/txt") .then() .statusCode(200) .body(is("hello")); given() .when().get("/hello/txt") .then() .statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) .body(is("Blocked by Sentinel (flow limiting)")); } @Test public void testSentinelAnnotationQuarkusAdapter() { given() .when().get("/hello/fallback/a") .then() .statusCode(200) .body(is("hello a")); given() .when().get("/hello/fallback/b") .then() .statusCode(200) .body(is("hello b")); given() .when().get("/hello/fallback/degrade") .then() .statusCode(200) .body(is("globalDefaultFallback, ex:test sentinel fallback")); given() .when().get("/hello/fallback/degrade") .then() .statusCode(200) .body(is("globalDefaultFallback, ex:test sentinel fallback")); given() .when().get("/hello/fallback/degrade") .then() .statusCode(200) .body(is("globalBlockHandler, ex:null")); given() .when().get("/hello/fallback/a") .then() .statusCode(200) .body(is("globalBlockHandler, ex:null")); given() .when().get("/hello/fallback2/a") .then() .statusCode(200) .body(is("hello a")); given() .when().get("/hello/fallback2/b") .then() .statusCode(200) .body(is("greetingFallback: FlowException")); } } ================================================ FILE: sentinel-demo/sentinel-demo-quarkus/src/test/java/com/alibaba/csp/sentinel/demo/quarkus/NativeGreetingResourceIT.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.quarkus; import io.quarkus.test.junit.NativeImageTest; @NativeImageTest public class NativeGreetingResourceIT extends GreetingResourceTest { // Execute the same tests but in native mode. } ================================================ FILE: sentinel-demo/sentinel-demo-rocketmq/README.md ================================================ # Sentinel RocketMQ Demo This demonstrates some specific scenarios for Apache RocketMQ client. ## Uniform Rate Limiting In Apache RocketMQ, when message consumers are consuming messages, there may a sudden inflow of messages, whether using pull or push mode. If all the messages were handled at this time, it would be likely to cause the system to be overloaded and then affect stability. However, in fact, there may be no messages coming within a few seconds. If redundant messages are directly discarded, the system's ability to process the message is not fully utilized. We hope that the sudden inflow of messages can be spread over a period of time, so that the system load can be kept on the stable level while processing as many messages as possible, thus achieving the effect of “shaving the peaks and filling the valley”. ![shaving the peaks and filling the valley](https://github.com/alibaba/Sentinel/wiki/image/mq-traffic-peak-clipping-en.png) Sentinel provides a feature for this kind of scenario: [Rate Limiter](https://github.com/alibaba/Sentinel/wiki/Flow-Shaping:-Pace-Limiter), which can spread a large number of sudden request inflow in a uniform rate manner, let the request pass at a fixed interval. It is often used to process burst requests instead of rejecting them. This avoids traffic spurs causing system overloaded. Moreover, the pending requests will be queued and processed one by one. When the request is estimated to exceed the maximum queuing timeout, it will be rejected immediately. For example, we configure the rule with uniform rate limiting mode and QPS count is 5, which indicates messages are consumed at fixed interval (200 ms) and pending messages will queue. We also set the maximum queuing timeout is 5s, then all requests estimated to exceed the timeout will be rejected immediately. ![Uniform rate](https://github.com/alibaba/Sentinel/wiki/image/uniform-speed-queue.png) ================================================ FILE: sentinel-demo/sentinel-demo-rocketmq/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-rocketmq org.apache.rocketmq rocketmq-client 4.2.0 ================================================ FILE: sentinel-demo/sentinel-demo-rocketmq/src/main/java/com/alibaba/csp/sentinel/demo/rocketmq/Constants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.rocketmq; public final class Constants { public static final String TEST_GROUP_NAME = "sentinel-group"; public static final String TEST_TOPIC_NAME = "SentinelTopicTest"; public static final String TEST_NAMESRV_ADDR = "127.0.0.1:9876"; private Constants() {} } ================================================ FILE: sentinel-demo/sentinel-demo-rocketmq/src/main/java/com/alibaba/csp/sentinel/demo/rocketmq/PullConsumerDemo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.rocketmq; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; public class PullConsumerDemo { private static final String KEY = String.format("%s:%s", Constants.TEST_GROUP_NAME, Constants.TEST_TOPIC_NAME); private static final Map OFFSET_TABLE = new HashMap(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ExecutorService pool = Executors.newFixedThreadPool(32); private static final AtomicLong SUCCESS_COUNT = new AtomicLong(0); private static final AtomicLong FAIL_COUNT = new AtomicLong(0); public static void main(String[] args) throws MQClientException { // First we init the flow control rule for Sentinel. initFlowControlRule(); DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(Constants.TEST_GROUP_NAME); consumer.setNamesrvAddr(Constants.TEST_NAMESRV_ADDR); consumer.start(); Set mqs = new HashSet<>(); try { mqs = consumer.fetchSubscribeMessageQueues(Constants.TEST_TOPIC_NAME); } catch (Exception e) { e.printStackTrace(); } for (MessageQueue mq : mqs) { System.out.printf("Consuming messages from the queue: %s%n", mq); SINGLE_MQ: while (true) { try { PullResult pullResult = consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32); if (pullResult.getMsgFoundList() != null) { for (MessageExt msg : pullResult.getMsgFoundList()) { doSomething(msg); } } long nextOffset = pullResult.getNextBeginOffset(); putMessageQueueOffset(mq, nextOffset); consumer.updateConsumeOffset(mq, nextOffset); switch (pullResult.getPullStatus()) { case NO_NEW_MSG: break SINGLE_MQ; case FOUND: case NO_MATCHED_MSG: case OFFSET_ILLEGAL: default: break; } } catch (Exception e) { e.printStackTrace(); } } } consumer.shutdown(); } private static void doSomething(MessageExt message) { pool.submit(() -> { Entry entry = null; try { ContextUtil.enter(KEY); entry = SphU.entry(KEY, EntryType.OUT); // Your business logic here. System.out.printf("[%d][%s][Success: %d] Receive New Messages: %s %n", System.currentTimeMillis(), Thread.currentThread().getName(), SUCCESS_COUNT.addAndGet(1), new String(message.getBody())); } catch (BlockException ex) { // Blocked. System.out.println("Blocked: " + FAIL_COUNT.addAndGet(1)); } finally { if (entry != null) { entry.exit(); } ContextUtil.exit(); } }); } private static void initFlowControlRule() { FlowRule rule = new FlowRule(); rule.setResource(KEY); // Indicates the interval between two adjacent requests is 200 ms. rule.setCount(5); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); // Enable rate limiting (uniform). This can ensure fixed intervals between two adjacent calls. // In this example, intervals between two incoming calls (message consumption) will be 200 ms constantly. rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // If more requests are coming, they'll be put into the waiting queue. // The queue has a queueing timeout. Requests that may exceed the timeout will be immediately blocked. // In this example, the max timeout is 5s. rule.setMaxQueueingTimeMs(5 * 1000); FlowRuleManager.loadRules(Collections.singletonList(rule)); } private static long getMessageQueueOffset(MessageQueue mq) { Long offset = OFFSET_TABLE.get(mq); if (offset != null) { return offset; } return 0; } private static void putMessageQueueOffset(MessageQueue mq, long offset) { OFFSET_TABLE.put(mq, offset); } } ================================================ FILE: sentinel-demo/sentinel-demo-rocketmq/src/main/java/com/alibaba/csp/sentinel/demo/rocketmq/SyncProducer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class SyncProducer { public static void main(String[] args) throws Exception { // Instantiate with a producer group name. DefaultMQProducer producer = new DefaultMQProducer(Constants.TEST_GROUP_NAME); producer.setNamesrvAddr(Constants.TEST_NAMESRV_ADDR); // Launch the instance. producer.start(); for (int i = 0; i < 1000; i++) { // Create a message instance, specifying topic, tag and message body. Message msg = new Message(Constants.TEST_TOPIC_NAME, "TagA", ("Hello RocketMQ From Sentinel " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) ); try { // Call send message to deliver message to one of brokers. SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } catch (Exception e) { e.printStackTrace(); } } // Shut down once the producer instance is not longer in use. producer.shutdown(); } } ================================================ FILE: sentinel-demo/sentinel-demo-servlet/pom.xml ================================================ com.alibaba.csp sentinel-parent ${revision} ../../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-servlet war javax.servlet javax.servlet-api 3.0.1 provided com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-web-servlet ${project.version} org.apache.maven.plugins maven-war-plugin 3.3.2 sentinel-demo-servlet ================================================ FILE: sentinel-demo/sentinel-demo-servlet/src/main/java/com/alibaba/csp/sentinel/demo/servlet/config/SentinelConfig.java ================================================ package com.alibaba.csp.sentinel.demo.servlet.config; import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlBlockHandler; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * class description * * @author zhangxunwei * @date 2024/6/24 */ public class SentinelConfig implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { initConfig(); } public static void initConfig() { System.out.println("Init sentinel config"); WebCallbackManager.setUrlBlockHandler(new DefaultUrlBlockHandler()); WebCallbackManager.setRequestOriginParser(request -> request.getHeader("S-user")); WebCallbackManager.setUrlCleaner(new MyUrlCleaner()); } static class MyUrlCleaner implements UrlCleaner { @Override public String clean(String originUrl) { if (originUrl.matches("/foo/\\d+")) { return "/foo/*"; } return originUrl; } } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } } ================================================ FILE: sentinel-demo/sentinel-demo-servlet/src/main/java/com/alibaba/csp/sentinel/demo/servlet/controller/DefaultServlet.java ================================================ package com.alibaba.csp.sentinel.demo.servlet.controller; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * class description * * @author zhangxunwei * @date 2024/6/24 */ public class DefaultServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String path = ((HttpServletRequest) servletRequest).getPathInfo(); if (path.startsWith("/foo")) { handleFoo(servletRequest, servletResponse); } else if (path.startsWith("/bar")) { handleBar(servletRequest, servletResponse); } else { notFound(servletRequest, servletResponse); } } private void notFound(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; httpServletResponse.setStatus(404); httpServletResponse.setContentType("text/plain"); httpServletResponse.getWriter().write(httpServletRequest.getServletPath() + " not found."); httpServletResponse.getWriter().close(); } private void handleBar(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; httpServletResponse.setStatus(200); httpServletResponse.setContentType("text/plain"); httpServletResponse.getWriter().write("bar"); httpServletResponse.getWriter().close(); } private void handleFoo(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; String path = httpServletRequest.getPathInfo(); String id = path.replaceAll("/foo/(\\d+)", "$1"); httpServletResponse.setStatus(200); httpServletResponse.setContentType("text/plain"); httpServletResponse.getWriter().write("Hello " + id); httpServletResponse.getWriter().close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } ================================================ FILE: sentinel-demo/sentinel-demo-servlet/src/main/resources/sentinel.properties ================================================ project.name=sentinel-demo-servlet csp.sentinel.dashboard.server=http://localhost:8081 ================================================ FILE: sentinel-demo/sentinel-demo-servlet/src/main/webapp/WEB-INF/web.xml ================================================ DefaultServlet com.alibaba.csp.sentinel.demo.servlet.controller.DefaultServlet DefaultServlet /* SentinelCommonFilter com.alibaba.csp.sentinel.adapter.servlet.CommonFilter HTTP_METHOD_SPECIFY true WEB_CONTEXT_UNIFY true SentinelCommonFilter /* ================================================ FILE: sentinel-demo/sentinel-demo-slot-spi/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-slot-spi ================================================ FILE: sentinel-demo/sentinel-demo-slot-spi/src/main/java/com/alibaba/csp/sentinel/demo/slot/DemoApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.slot; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Demo for adding custom slot. * @see {@link DemoSlot}. * * @author Eric Zhao * @author cdfive */ public class DemoApplication { public static void main(String[] args) { Entry entry = null; try { entry = SphU.entry("abc"); } catch (BlockException ex) { ex.printStackTrace(); } finally { if (entry != null) { entry.exit(); } } } } ================================================ FILE: sentinel-demo/sentinel-demo-slot-spi/src/main/java/com/alibaba/csp/sentinel/demo/slot/DemoSlot.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.slot; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; import com.alibaba.csp.sentinel.spi.Spi; /** * A demo slot that records current context and entry resource. * * Note that the value of order attribute in `@Spi` is -1500, the smaller the value, the higher the order, * so this slot will be executed after {@link FlowSlot}(order=-2000) and before {@link DegradeSlot}(order=-1000), * refer to the constants for slot order definitions in {@link Constants}. * * @author Eric Zhao * @author cdfive */ @Spi(order = -1500) public class DemoSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { System.out.println("------Entering for entry on DemoSlot------"); System.out.println("Current context: " + context.getName()); System.out.println("Current entry resource: " + context.getCurEntry().getResourceWrapper().getName()); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { System.out.println("------Exiting for entry on DemoSlot------"); System.out.println("Current context: " + context.getName()); System.out.println("Current entry resource: " + context.getCurEntry().getResourceWrapper().getName()); fireExit(context, resourceWrapper, count, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-slot-spi/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot ================================================ # Custom ProcessorSlot com.alibaba.csp.sentinel.demo.slot.DemoSlot ================================================ FILE: sentinel-demo/sentinel-demo-slotchain-spi/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-slotchain-spi ================================================ FILE: sentinel-demo/sentinel-demo-slotchain-spi/src/main/java/com/alibaba/csp/sentinel/demo/slotchain/DemoDegradeRuleApplication.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.slotchain; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * Demo for degrade rule using custom SlotChainBuilder {@link DemoSlotChainBuilder}. * * You will see this in sentinel-record.log, indicating that the custom slot chain builder is activated: * [SlotChainProvider] Global slot chain builder resolved: com.alibaba.csp.sentinel.demo.slotchain.DemoSlotChainBuilder * * @author cdfive */ public class DemoDegradeRuleApplication { private static final String RESOURCE_KEY = "abc"; public static void main(String[] args) throws Exception { initDegradeRule(); for (int i = 1; i <= 100; i++) { Entry entry = null; try { entry = SphU.entry(RESOURCE_KEY); TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(10, 100)); System.out.println(i + "=>" + " passed"); } catch (BlockException ex) { System.out.println(i + "=>" + " blocked by " + ex.getClass().getSimpleName()); } finally { if (entry != null) { entry.exit(); } } } } private static void initDegradeRule() { List rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(RESOURCE_KEY) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) // Max allowed response time .setCount(20) // Retry timeout (in second) .setTimeWindow(10) // Circuit breaker opens when slow request ratio > 20% .setSlowRatioThreshold(0.2) .setMinRequestAmount(10) .setStatIntervalMs(20000); rules.add(rule); DegradeRuleManager.loadRules(rules); System.out.println("Degrade rule loaded: " + rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-slotchain-spi/src/main/java/com/alibaba/csp/sentinel/demo/slotchain/DemoFlowRuleApplication.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.slotchain; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * Demo for flow rule using custom SlotChainBuilder {@link DemoSlotChainBuilder}. * * You will see this in sentinel-record.log, indicating that the custom slot chain builder is activated: * [SlotChainProvider] Global slot chain builder resolved: com.alibaba.csp.sentinel.demo.slotchain.DemoSlotChainBuilder * * @author cdfive */ public class DemoFlowRuleApplication { private static final String RESOURCE_KEY = "abc"; public static void main(String[] args) throws Exception { initFlowQpsRule(); for (int i = 1; i <= 100; i++) { Entry entry = null; try { entry = SphU.entry(RESOURCE_KEY); TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(10, 100)); System.out.println(i + "=>" + " passed"); } catch (BlockException ex) { System.out.println(i + "=>" + " blocked by " + ex.getClass().getSimpleName()); } finally { if (entry != null) { entry.exit(); } } } } private static void initFlowQpsRule() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource(RESOURCE_KEY); // set limit qps to 5 rule1.setCount(5); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); System.out.println("Flow rule loaded: " + rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-slotchain-spi/src/main/java/com/alibaba/csp/sentinel/demo/slotchain/DemoSlotChainBuilder.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.slotchain; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.*; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.spi.SpiLoader; import java.util.List; /** * A demo {@link SlotChainBuilder} for build custom slot chain. * Two ways to build slot chain are demonstrated. * * Pay attention to that `ProcessorSlotChain` is not a SPI, but the `SlotChainBuilder`. * * Most of the time, we don't need to customize `SlotChainBuilder`, * maybe customize `ProcessorSlot` is enough, refer to `sentinel-demo-slot-spi` module. * * Note that the sentinel's default slots and the order of them are very important, be careful when customizing, * refer to the constants for slot order definitions in {@link Constants}. * You may also refer to {@link DefaultSlotChainBuilder}. * * @author cdfive */ @Spi public class DemoSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); List sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); // Filter out `DegradeSlot` // Test for `DemoDegradeRuleApplication`, the demo will not be blocked by `DegradeException` sortedSlotList.removeIf(o -> DegradeSlot.class.equals(o.getClass())); for (ProcessorSlot slot : sortedSlotList) { if (!(slot instanceof AbstractLinkedProcessorSlot)) { RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); continue; } chain.addLast((AbstractLinkedProcessorSlot) slot); } return chain; } /** * Another way to build the slot chain, add slot one by one with `SpiLoader#loadInstance`. * Note that the sentinel's default slots and the order of them are very important, be careful when customizing, * refer to the constants for slot order definitions in {@link com.alibaba.csp.sentinel.Constants}. */ /* @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); // Create a `SpiLoader` instance SpiLoader spiLoader = SpiLoader.of(ProcessorSlot.class); // Add `NodeSelectorSlot`, load by class chain.addLast((AbstractLinkedProcessorSlot) spiLoader.loadInstance(NodeSelectorSlot.class)); // Add `ClusterBuilderSlot`, load by aliasname(default is classname) chain.addLast((AbstractLinkedProcessorSlot) spiLoader.loadInstance("com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot")); // Add `StatisticSlot` chain.addLast((AbstractLinkedProcessorSlot) spiLoader.loadInstance(StatisticSlot.class)); // Add `FlowSlot` chain.addLast((AbstractLinkedProcessorSlot) spiLoader.loadInstance(FlowSlot.class)); // Add `DegradeSlot` // Test for `DemoDegradeRuleApplication` // If we don't add `DegradeSlot`, the demo will not be blocked by `DegradeException` // If it's added, we can see the expected DegradeException // chain.addLast((AbstractLinkedProcessorSlot) spiLoader.loadInstance(DegradeSlot.class)); return chain; } */ } ================================================ FILE: sentinel-demo/sentinel-demo-slotchain-spi/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder ================================================ # Custom SlotChainBuilder to build slot chain com.alibaba.csp.sentinel.demo.slotchain.DemoSlotChainBuilder ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/README.md ================================================ # Sentinel SOFARPC Demo Sentinel 提供了与 SOFARPC 整合的模块 - `sentinel-sofa-rpc-adapter`,主要包括针对 Service Provider 和 Service Consumer 实现的 Filter。使用时用户只需引入以下模块(以 Maven 为例): ```xml com.alibaba.csp sentinel-sofa-rpc-adapter x.y.z ``` 引入此依赖后,SOFARPC 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。 > **注:若希望接入 Dashboard,请参考 demo 中的注释添加启动参数。只引入 `sentinel-sofa-rpc-adapter` 依赖无法接入控制台!** 若不希望开启 Sentinel SOFARPC Adapter 中的某个 Filter,可以手动关闭对应的 Filter,比如: ```java providerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); ``` 或者在 `rpc-config.json` 文件中设置,它的优先级要低一些。 ```json { "sofa.rpc.sentinel.enabled": true } ``` ## 运行 Demo 1. 启动控制台,运行 `DashboardApplication` 2. 启动 Provider,运行 `DemoProvider`(VM参数:`-Dproject.name=DemoProvider -Dcsp.sentinel.dashboard.server=localhost:8080`) 3. 启动 Consumer,运行 `DemoConsumer`(VM参数:`-Dproject.name=DemoConsumer -Dcsp.sentinel.dashboard.server=localhost:8080`) 通过控制台实时监控、簇点链路菜单观察接口调用、资源情况;对资源设置不同流控规则,进行观察和调试。 参考:[Sentinel 控制台文档](https://github.com/alibaba/Sentinel/wiki/控制台). ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-sofa-rpc 5.6.4 1.7.21 com.alibaba.csp sentinel-sofa-rpc-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ${project.version} com.alipay.sofa sofa-rpc-all ${sofa-rpc-all.version} org.slf4j slf4j-log4j12 ${slf4j-log4j12.version} ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/java/com/alibaba/csp/sentinel/demo/sofa/rpc/DemoConsumer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.sofa.rpc; import com.alibaba.csp.sentinel.demo.sofa.rpc.service.DemoService; import com.alipay.sofa.rpc.common.RpcConstants; import com.alipay.sofa.rpc.config.ApplicationConfig; import com.alipay.sofa.rpc.config.ConsumerConfig; import java.util.concurrent.TimeUnit; /** * Demo consumer of SOFARPC. * * Interact with Sentinel Dashboard, add the following VM arguments: *
           * -Dproject.name=DemoProvider -Dcsp.sentinel.dashboard.server=localhost:8080
           * 
          * * @author cdfive */ public class DemoConsumer { public static void main(String[] args) throws Exception { ApplicationConfig application = new ApplicationConfig().setAppName("DemoConsumer"); ConsumerConfig consumerConfig = new ConsumerConfig() .setApplication(application) .setInterfaceId(DemoService.class.getName()) .setProtocol("bolt") .setDirectUrl("bolt://127.0.0.1:12001") .setInvokeType(RpcConstants.INVOKER_TYPE_SYNC); // 设置是否启用Sentinel,默认启用 // 也可在rpc-config.json全局设置 // consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); DemoService helloService = consumerConfig.refer(); System.out.println("DemoConsumer started!"); long sleepMs = 5; int total = 5000; int index = 0; System.out.println("Total call " + total + " times and sleep " + sleepMs + "ms after each call."); while (true) { try { index++; String result = helloService.sayHello(index, "SOFARPC", 2020); System.out.println("[" + index + "][Consumer]receive response: " + result); } catch (Exception e) { System.out.println("[" + index + "][Consumer]receive exception: " + e.getMessage()); } TimeUnit.MILLISECONDS.sleep(sleepMs); if (index == total) { break; } } System.out.println("DemoConsumer exit!"); System.exit(0); } } ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/java/com/alibaba/csp/sentinel/demo/sofa/rpc/DemoProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.sofa.rpc; import com.alibaba.csp.sentinel.demo.sofa.rpc.service.DemoService; import com.alibaba.csp.sentinel.demo.sofa.rpc.service.impl.DemoServiceImpl; import com.alipay.sofa.rpc.config.ProviderConfig; import com.alipay.sofa.rpc.config.ServerConfig; /** * Demo provider of SOFARPC * * Interact with Sentinel Dashboard, add the following VM arguments: *
           * -Dproject.name=DemoProvider -Dcsp.sentinel.dashboard.server=localhost:8080
           * 
          * * @author cdfive */ public class DemoProvider { public static void main(String[] args) { ServerConfig serverConfig = new ServerConfig() .setProtocol("bolt") .setPort(12001) .setDaemon(false); ProviderConfig providerConfig = new ProviderConfig() .setInterfaceId(DemoService.class.getName()) .setRef(new DemoServiceImpl()) .setServer(serverConfig); // 设置是否启用Sentinel,默认启用 // 也可在rpc-config.json全局设置 // providerConfig.setParameter("sofa.rpc.sentinel.enabled", "false"); providerConfig.export(); System.out.println("DemoProvider started!"); } } ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/java/com/alibaba/csp/sentinel/demo/sofa/rpc/service/DemoService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.sofa.rpc.service; /** * @author cdfive */ public interface DemoService { String sayHello(Integer index, String name, int year); } ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/java/com/alibaba/csp/sentinel/demo/sofa/rpc/service/impl/DemoServiceImpl.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.sofa.rpc.service.impl; import com.alibaba.csp.sentinel.demo.sofa.rpc.service.DemoService; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * @author cdfive */ public class DemoServiceImpl implements DemoService { @Override public String sayHello(Integer index, String name, int year) { System.out.println("[" + index + "][Provider]receive request: " + name + "," + year); int sleepMs = ThreadLocalRandom.current().nextInt(50); try { TimeUnit.MILLISECONDS.sleep(sleepMs); } catch (InterruptedException e) { System.err.println(e.getMessage()); } return "Hello " + name + " " + year + "[" + sleepMs + "ms]"; } } ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/resources/log4j.xml ================================================ ================================================ FILE: sentinel-demo/sentinel-demo-sofa-rpc/src/main/resources/sofa-rpc/rpc-config.json ================================================ { "rpc.config.order": 999, "logger.impl": "com.alipay.sofa.rpc.log.SLF4JLoggerImpl", // 是否启用Sentinel,不设置默认为true "sofa.rpc.sentinel.enabled": true } ================================================ FILE: sentinel-demo/sentinel-demo-spring-cloud-gateway/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-spring-cloud-gateway org.springframework.cloud spring-cloud-starter-gateway 3.1.1 org.springframework.boot spring-boot-starter-webflux 2.6.6 com.alibaba.csp sentinel-spring-cloud-gateway-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ================================================ FILE: sentinel-demo/sentinel-demo-spring-cloud-gateway/src/main/java/com/alibaba/csp/sentinel/demo/spring/sc/gateway/GatewayConfiguration.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.sc.gateway; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** * @author Eric Zhao */ @Configuration public class GatewayConfiguration { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { initCustomizedApis(); initGatewayRules(); } private void initCustomizedApis() { Set definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/ahas")); add(new ApiPathPredicateItem().setPattern("/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("another_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules() { Set rules = new HashSet<>(); rules.add(new GatewayFlowRule("aliyun_route") .setCount(10) .setIntervalSec(1) ); rules.add(new GatewayFlowRule("aliyun_route") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pa") ) ); rules.add(new GatewayFlowRule("httpbin_route") .setCount(2) .setIntervalSec(30) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("type") .setPattern("warn") .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS) ) ); rules.add(new GatewayFlowRule("some_customized_api") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pn") ) ); GatewayRuleManager.loadRules(rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-cloud-gateway/src/main/java/com/alibaba/csp/sentinel/demo/spring/sc/gateway/GatewayDemoApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.sc.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *

          A demo for Spring Cloud Gateway.

          * *

          To integrate with Sentinel dashboard, you can run the demo with the parameters (an example): * -Dproject.name=spring-cloud-gateway -Dcsp.sentinel.dashboard.server=localhost:8080 * -Dcsp.sentinel.api.port=8720 -Dcsp.sentinel.app.type=1 * *

          * * @author Eric Zhao */ @SpringBootApplication public class GatewayDemoApplication { public static void main(String[] args) { SpringApplication.run(GatewayDemoApplication.class, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-cloud-gateway/src/main/resources/application.yml ================================================ server: port: 8090 spring: application: name: spring-cloud-gateway cloud: gateway: enabled: true discovery: locator: lower-case-service-id: true routes: # Add your routes here. - id: aliyun_route uri: https://www.aliyun.com/ predicates: - Path=/product/** - id: httpbin_route uri: https://httpbin.org predicates: - Path=/httpbin/** filters: - RewritePath=/httpbin/(?.*), /$\{segment} ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-spring-webflux 2.5.12 com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-spring-webflux-adapter ${project.version} org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} org.springframework.boot spring-boot-starter-data-redis-reactive ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/WebFluxDemoApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *

          A demo for Spring WebFlux reactive application.

          * *

          To integrate with Sentinel dashboard, you can run the demo with the parameters (an example): * -Dproject.name=WebFluxDemoApplication -Dcsp.sentinel.dashboard.server=localhost:8080 * -Dcsp.sentinel.api.port=8720 * *

          * * @author Eric Zhao */ @SpringBootApplication public class WebFluxDemoApplication { public static void main(String[] args) { SpringApplication.run(WebFluxDemoApplication.class, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/RedisConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author Eric Zhao */ @Configuration public class RedisConfig { @Bean public ReactiveRedisTemplate stringReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory){ RedisSerializationContext serializationContext = RedisSerializationContext .newSerializationContext(new StringRedisSerializer()) .hashKey(new StringRedisSerializer()) .hashValue(new StringRedisSerializer()) .build(); return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/WebFluxConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.config; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter; import com.alibaba.csp.sentinel.adapter.spring.webflux.exception.SentinelBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** * @author Eric Zhao */ public class WebFluxConfig { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public WebFluxConfig(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(-1) public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { // Register the block exception handler for Spring WebFlux. return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) public SentinelWebFluxFilter sentinelWebFluxFilter() { // Register the Sentinel WebFlux filter. return new SentinelWebFluxFilter(); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/BazController.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.controller; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.demo.spring.webflux.service.BazService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; /** * @author Eric Zhao */ @RestController @RequestMapping(value = "/baz") public class BazController { @Autowired private BazService bazService; @GetMapping("/{id}") public Mono apiGetValue(@PathVariable("id") Long id) { return bazService.getById(id) .transform(new SentinelReactorTransformer<>("BazService:getById")); } @PostMapping("/{id}") public Mono apiSetValue(@PathVariable("id") Long id, @RequestBody String value) { return bazService.setValue(id, value) .transform(new SentinelReactorTransformer<>("BazService:setValue")); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/FooController.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.controller; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.demo.spring.webflux.service.FooService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author Eric Zhao */ @RestController @RequestMapping(value = "/foo") public class FooController { @Autowired private FooService fooService; @GetMapping("/single") public Mono apiNormalSingle() { return fooService.emitSingle() // transform the publisher here. .transform(new SentinelReactorTransformer<>("demo_foo_normal_single")); } @GetMapping("/flux") public Flux apiNormalFlux() { return fooService.emitMultiple() .transform(new SentinelReactorTransformer<>("demo_foo_normal_flux")); } @GetMapping("/slow") public Mono apiDoSomethingSlow(ServerHttpResponse response) { return fooService.doSomethingSlow(); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/BazService.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; /** *

          A sample service for interacting with Redis via reactive Redis client.

          *

          To play this service, you need a Redis instance running in local.

          * * @author Eric Zhao */ @Service public class BazService { @Autowired private ReactiveRedisTemplate template; public Mono getById(Long id) { if (id == null || id <= 0) { return Mono.error(new IllegalArgumentException("invalid id: " + id)); } return template.opsForValue() .get(KEY_PREFIX + id) .switchIfEmpty(Mono.just("not_found")); } public Mono setValue(Long id, String value) { if (id == null || id <= 0 || value == null) { return Mono.error(new IllegalArgumentException("invalid parameters")); } return template.opsForValue() .set(KEY_PREFIX + id, value); } private static final String KEY_PREFIX = "sentinel-reactor-test:"; } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/FooService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webflux.service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; /** * @author Eric Zhao */ @Service public class FooService { @SuppressWarnings("PMD.ThreadPoolCreationRule") private final ExecutorService pool = Executors.newFixedThreadPool(8); private final Scheduler scheduler = Schedulers.fromExecutor(pool); public Mono emitSingle() { return Mono.just(ThreadLocalRandom.current().nextInt(0, 2000)) .map(e -> e + "d"); } public Flux emitMultiple() { int start = ThreadLocalRandom.current().nextInt(0, 6000); return Flux.range(start, 10); } public Mono doSomethingSlow() { return Mono.fromCallable(() -> { Thread.sleep(2000); System.out.println("doSomethingSlow: " + Thread.currentThread().getName()); return "ok"; }).publishOn(scheduler); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webflux/src/main/resources/application.properties ================================================ spring.application.name=sentinel-webflux-demo-application server.port=8081 ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-spring-webmvc 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-spring-webmvc-adapter ${project.version} org.springframework.boot spring-boot-starter-web ${spring.boot.version} org.springframework.boot spring-boot-starter-test ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/WebMvcDemoApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webmvc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *

          Add the JVM parameter to connect to the dashboard:

          * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-spring-webmvc} * * @author kaizi2009 */ @SpringBootApplication public class WebMvcDemoApplication { public static void main(String[] args) { SpringApplication.run(WebMvcDemoApplication.class); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/InterceptorConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webmvc.config; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelExceptionAware; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebTotalInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcTotalConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Config sentinel interceptor * * @author kaizi2009 */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // Add Sentinel interceptor addSpringMvcInterceptor(registry); } @Bean public SentinelExceptionAware sentinelExceptionAware() { //Make exception visible to Sentinel if you have configured ExceptionHandler return new SentinelExceptionAware(); } private void addSpringMvcInterceptor(InterceptorRegistry registry) { SentinelWebMvcConfig config = new SentinelWebMvcConfig(); // Depending on your situation, you can choose to process the BlockException via // the BlockExceptionHandler or throw it directly, then handle it // in Spring web global exception handler. // config.setBlockExceptionHandler((request, response, e) -> { throw e; }); // Use the default handler. config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); // Custom configuration if necessary config.setHttpMethodSpecify(true); // By default web context is true, means that unify web context(i.e. use the default context name), // in most scenarios that's enough, and it could reduce the memory footprint. // If set it to false, entrance contexts will be separated by different URLs, // which is useful to support "chain" relation flow strategy. // We can change it and view different result in `Resource Chain` menu of dashboard. config.setWebContextUnify(true); config.setOriginParser(request -> request.getHeader("S-user")); // Add sentinel interceptor registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**"); } private void addSpringMvcTotalInterceptor(InterceptorRegistry registry) { //Config SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig(); //Custom configuration if necessary config.setRequestAttributeName("my_sentinel_spring_mvc_total_entity_container"); config.setTotalResourceName("my-spring-mvc-total-url-request"); //Add sentinel interceptor registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**"); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/config/SentinelSpringMvcBlockHandlerConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webmvc.config; import com.alibaba.csp.sentinel.demo.spring.webmvc.vo.ResultWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * Spring configuration for global exception handler. * This will be activated when the {@code BlockExceptionHandler} * throws {@link BlockException directly}. * * @author kaizi2009 */ @ControllerAdvice @Order(0) public class SentinelSpringMvcBlockHandlerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(BlockException.class) @ResponseBody public ResultWrapper sentinelBlockHandler(BlockException e) { logger.warn("Blocked by Sentinel: {}", e.getRule()); // Return the customized result. return ResultWrapper.blocked(); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/controller/WebMvcTestController.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webmvc.controller; import java.util.Random; import java.util.concurrent.TimeUnit; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.servlet.ModelAndView; /** * Test controller * * @author kaizi2009 */ @Controller public class WebMvcTestController { @GetMapping("/hello") @ResponseBody public String apiHello() { doBusiness(); return "Hello!"; } @GetMapping("/err") @ResponseBody public String apiError() { doBusiness(); return "Oops..."; } @GetMapping("/foo/{id}") @ResponseBody public String apiFoo(@PathVariable("id") Long id) { doBusiness(); return "Hello " + id; } @GetMapping("/exclude/{id}") @ResponseBody public String apiExclude(@PathVariable("id") Long id) { doBusiness(); return "Exclude " + id; } @GetMapping("/forward") public ModelAndView apiForward() { ModelAndView mav = new ModelAndView(); mav.setViewName("hello"); return mav; } @GetMapping("/async") @ResponseBody public DeferredResult distribute() throws Exception { DeferredResult result = new DeferredResult<>(4000L); Thread thread = new Thread(() -> result.setResult("async result")); thread.start(); return result; } private void doBusiness() { Random random = new Random(1); try { TimeUnit.MILLISECONDS.sleep(random.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/java/com/alibaba/csp/sentinel/demo/spring/webmvc/vo/ResultWrapper.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.spring.webmvc.vo; import com.alibaba.fastjson.JSONObject; /** * @author kaizi2009 */ public class ResultWrapper { private Integer code; private String message; public ResultWrapper(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public static ResultWrapper blocked() { return new ResultWrapper(-1, "Blocked by Sentinel"); } public String toJsonString() { return JSONObject.toJSONString(this); } } ================================================ FILE: sentinel-demo/sentinel-demo-spring-webmvc/src/main/resources/application.properties ================================================ server.port=10000 ================================================ FILE: sentinel-demo/sentinel-demo-transport-spring-mvc/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-transport-spring-mvc 2.5.12 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-spring-mvc org.springframework.boot spring-boot-starter-web ${spring.boot.version} ================================================ FILE: sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/demo/transport/springmvc/TransportSpringMvcDemoApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.transport.springmvc; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.transport.command.SentinelApiHandlerAdapter; import com.alibaba.csp.sentinel.transport.command.SentinelApiHandlerMapping; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; /** *

          Add the JVM parameter to connect to the dashboard:

          * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-transport-spring-mvc} * *

          Add the JVM parameter to tell dashboard your application port:

          * {@code -Dcsp.sentinel.api.port=10000} * * @author shenbaoyong */ @SpringBootApplication @Controller public class TransportSpringMvcDemoApplication { public static void main(String[] args) { triggerSentinelInit(); initFlowRules(); SpringApplication.run(TransportSpringMvcDemoApplication.class); } public static void initFlowRules() { List rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("demo-hello-api"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); FlowRuleManager.loadRules(rules); } @GetMapping("/hello") @ResponseBody public String hello() { Entry entry = null; try { entry = SphU.entry("demo-hello-api"); return "ok: " + LocalDateTime.now(); } catch (BlockException e1) { return "helloBlockHandler: " + LocalDateTime.now(); } finally { if (entry != null) { entry.exit(); } } } private static void triggerSentinelInit() { new Thread(() -> InitExecutor.doInit()).start(); } @Bean public SentinelApiHandlerMapping sentinelApiHandlerMapping() { return new SentinelApiHandlerMapping(); } @Bean public SentinelApiHandlerAdapter sentinelApiHandlerAdapter() { return new SentinelApiHandlerAdapter(); } } ================================================ FILE: sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/resources/application.properties ================================================ server.port=10000 ================================================ FILE: sentinel-demo/sentinel-demo-zookeeper-datasource/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-zookeeper-datasource 3.4.14 4.0.1 com.alibaba.csp sentinel-core com.alibaba.csp sentinel-datasource-extension com.alibaba.csp sentinel-datasource-zookeeper com.alibaba fastjson org.apache.zookeeper zookeeper ${zookeeper.version} org.apache.curator curator-recipes ${curator.version} org.apache.zookeeper zookeeper ================================================ FILE: sentinel-demo/sentinel-demo-zookeeper-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/zookeeper/ZookeeperConfigSender.java ================================================ package com.alibaba.csp.sentinel.demo.datasource.zookeeper; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; /** * Zookeeper config sender for demo * * @author guonanjun */ public class ZookeeperConfigSender { private static final int RETRY_TIMES = 3; private static final int SLEEP_TIME = 1000; public static void main(String[] args) throws Exception { final String remoteAddress = "localhost:2181"; final String groupId = "Sentinel-Demo"; final String dataId = "SYSTEM-CODE-DEMO-FLOW"; final String rule = "[\n" + " {\n" + " \"resource\": \"TestResource\",\n" + " \"controlBehavior\": 0,\n" + " \"count\": 10.0,\n" + " \"grade\": 1,\n" + " \"limitApp\": \"default\",\n" + " \"strategy\": 0\n" + " }\n" + "]"; CuratorFramework zkClient = CuratorFrameworkFactory.newClient(remoteAddress, new ExponentialBackoffRetry(SLEEP_TIME, RETRY_TIMES)); zkClient.start(); String path = getPath(groupId, dataId); Stat stat = zkClient.checkExists().forPath(path); if (stat == null) { zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, null); } zkClient.setData().forPath(path, rule.getBytes()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } zkClient.close(); } private static String getPath(String groupId, String dataId) { String path = ""; if (groupId.startsWith("/")) { path += groupId; } else { path += "/" + groupId; } if (dataId.startsWith("/")) { path += dataId; } else { path += "/" + dataId; } return path; } } ================================================ FILE: sentinel-demo/sentinel-demo-zookeeper-datasource/src/main/java/com/alibaba/csp/sentinel/demo/datasource/zookeeper/ZookeeperDataSourceDemo.java ================================================ package com.alibaba.csp.sentinel.demo.datasource.zookeeper; import java.util.List; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** * Zookeeper ReadableDataSource Demo * * @author guonanjun */ public class ZookeeperDataSourceDemo { public static void main(String[] args) { // 使用zookeeper的场景 loadRules(); // 方便扩展的场景 //loadRules2(); } private static void loadRules() { final String remoteAddress = "127.0.0.1:2181"; final String path = "/Sentinel-Demo/SYSTEM-CODE-DEMO-FLOW"; ReadableDataSource> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, path, source -> JSON.parseObject(source, new TypeReference>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } private static void loadRules2() { final String remoteAddress = "127.0.0.1:2181"; // 引入groupId和dataId的概念,是为了方便和Nacos进行切换 final String groupId = "Sentinel-Demo"; final String flowDataId = "SYSTEM-CODE-DEMO-FLOW"; // final String degradeDataId = "SYSTEM-CODE-DEMO-DEGRADE"; // final String systemDataId = "SYSTEM-CODE-DEMO-SYSTEM"; // 规则会持久化到zk的/groupId/flowDataId节点 // groupId和和flowDataId可以用/开头也可以不用 // 建议不用以/开头,目的是为了如果从Zookeeper切换到Nacos的话,只需要改数据源类名就可以 ReadableDataSource> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, groupId, flowDataId, source -> JSON.parseObject(source, new TypeReference>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); // ReadableDataSource> degradeRuleDataSource = new ZookeeperDataSource<>(remoteAddress, groupId, degradeDataId, // source -> JSON.parseObject(source, new TypeReference>() {})); // DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty()); // // ReadableDataSource> systemRuleDataSource = new ZookeeperDataSource<>(remoteAddress, groupId, systemDataId, // source -> JSON.parseObject(source, new TypeReference>() {})); // SystemRuleManager.register2Property(systemRuleDataSource.getProperty()); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul-gateway/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-zuul-gateway org.springframework.cloud spring-cloud-starter-netflix-zuul 2.2.10.RELEASE com.fasterxml.jackson.core jackson-databind 2.13.2.1 com.alibaba.csp sentinel-zuul-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http ================================================ FILE: sentinel-demo/sentinel-demo-zuul-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul/gateway/GatewayRuleConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.zuul.gateway; import java.util.HashSet; import java.util.Set; import javax.annotation.PostConstruct; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration public class GatewayRuleConfig { @PostConstruct public void doInit() { // Prepare some gateway rules and API definitions (only for demo). // It's recommended to leverage dynamic data source or the Sentinel dashboard to push the rules. initCustomizedApis(); initGatewayRules(); } private void initCustomizedApis() { Set definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/ahas")); add(new ApiPathPredicateItem().setPattern("/aliyun_product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("another_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules() { Set rules = new HashSet<>(); rules.add(new GatewayFlowRule("aliyun-product-route") .setCount(10) .setIntervalSec(1) ); rules.add(new GatewayFlowRule("aliyun-product-route") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ) ); rules.add(new GatewayFlowRule("another-route-httpbin") .setCount(10) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(600) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ) ); rules.add(new GatewayFlowRule("another-route-httpbin") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pa") ) ); rules.add(new GatewayFlowRule("some_customized_api") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pn") ) ); GatewayRuleManager.loadRules(rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul/gateway/ZuulConfig.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.zuul.gateway; import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter; import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter; import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter; import com.netflix.zuul.ZuulFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao */ @Configuration public class ZuulConfig { @Bean public ZuulFilter sentinelZuulPreFilter() { return new SentinelZuulPreFilter(10000); } @Bean public ZuulFilter sentinelZuulPostFilter() { return new SentinelZuulPostFilter(1000); } @Bean public ZuulFilter sentinelZuulErrorFilter() { return new SentinelZuulErrorFilter(-1); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul/gateway/ZuulGatewayDemoApplication.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.zuul.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** *

          A demo for using Zuul 1.x with Spring Cloud and Sentinel.

          * *

          To integrate with Sentinel dashboard, you can run the demo with the parameters (an example): * * -Dproject.name=zuul-gateway -Dcsp.sentinel.dashboard.server=localhost:8080 * -Dcsp.sentinel.api.port=8720 -Dcsp.sentinel.app.type=1 * *

          * * @author Eric Zhao */ @SpringBootApplication @EnableZuulProxy public class ZuulGatewayDemoApplication { public static void main(String[] args) { SpringApplication.run(ZuulGatewayDemoApplication.class, args); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul-gateway/src/main/resources/application.yml ================================================ server: port: 8097 spring: application: name: zuul-gateway zuul: routes: aliyun-product-route: path: /aliyun_product/** url: https://www.aliyun.com/product another-route-httpbin: path: /another/** url: https://httpbin.org ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/pom.xml ================================================ com.alibaba.csp sentinel-demo ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-demo-zuul2-gateway com.alibaba.csp sentinel-zuul2-adapter ${project.version} com.alibaba.csp sentinel-transport-simple-http com.netflix.zuul zuul-core 2.1.5 javax.annotation javax.annotation-api ${javax.annotation-api.version} org.slf4j slf4j-api 1.7.36 org.slf4j slf4j-log4j12 1.7.36 org.apache.httpcomponents httpclient 4.5.8 ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/FiltersRegisteringService.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; import com.netflix.zuul.filters.FilterRegistry; import com.netflix.zuul.filters.ZuulFilter; public class FiltersRegisteringService { private final List filters; private final FilterRegistry filterRegistry; @Inject public FiltersRegisteringService(FilterRegistry filterRegistry, Set filters) { this.filters = new ArrayList<>(filters); this.filterRegistry = filterRegistry; } public List getFilters() { return filters; } @PostConstruct public void initialize() { for (ZuulFilter filter: filters) { this.filterRegistry.put(filter.filterName(), filter); } } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/GatewayRuleConfig.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import java.util.HashSet; import java.util.Set; public class GatewayRuleConfig { public void doInit() { // Prepare some gateway rules and API definitions (only for demo). // It's recommended to leverage dynamic data source or the Sentinel dashboard to push the rules. initCustomizedApis(); initGatewayRules(); } private void initCustomizedApis() { Set definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/images")); add(new ApiPathPredicateItem().setPattern("/comments") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("another_customized_api") .setPredicateItems(new HashSet() {{ add(new ApiPathPredicateItem().setPattern("/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } private void initGatewayRules() { Set rules = new HashSet<>(); rules.add(new GatewayFlowRule("images") .setCount(10) .setIntervalSec(1) ); rules.add(new GatewayFlowRule("images") .setCount(2) .setIntervalSec(2) .setBurst(2) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) ) ); rules.add(new GatewayFlowRule("comments") .setCount(3) .setIntervalSec(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) .setMaxQueueingTimeoutMs(6000) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) .setFieldName("X-Sentinel-Flag") ) ); rules.add(new GatewayFlowRule("comments") .setCount(1) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pa") ) ); rules.add(new GatewayFlowRule("some_customized_api") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(5) .setIntervalSec(1) .setParamItem(new GatewayParamFlowItem() .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) .setFieldName("pn") ) ); GatewayRuleManager.loadRules(rules); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/SampleServerStartup.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.config.DynamicIntProperty; import com.netflix.discovery.EurekaClient; import com.netflix.netty.common.accesslog.AccessLogPublisher; import com.netflix.netty.common.channel.config.ChannelConfig; import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; import com.netflix.netty.common.metrics.EventLoopGroupMetrics; import com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler; import com.netflix.netty.common.ssl.ServerSslConfig; import com.netflix.netty.common.status.ServerStatusManager; import com.netflix.spectator.api.Registry; import com.netflix.zuul.FilterLoader; import com.netflix.zuul.FilterUsageNotifier; import com.netflix.zuul.RequestCompleteHandler; import com.netflix.zuul.context.SessionContextDecorator; import com.netflix.zuul.netty.server.BaseServerStartup; import com.netflix.zuul.netty.server.DirectMemoryMonitor; import com.netflix.zuul.netty.server.ZuulServerChannelInitializer; import io.netty.channel.ChannelInitializer; import io.netty.channel.group.ChannelGroup; @Singleton public class SampleServerStartup extends BaseServerStartup { @Inject public SampleServerStartup(ServerStatusManager serverStatusManager, FilterLoader filterLoader, SessionContextDecorator sessionCtxDecorator, FilterUsageNotifier usageNotifier, RequestCompleteHandler reqCompleteHandler, Registry registry, DirectMemoryMonitor directMemoryMonitor, EventLoopGroupMetrics eventLoopGroupMetrics, EurekaClient discoveryClient, ApplicationInfoManager applicationInfoManager, AccessLogPublisher accessLogPublisher) { super(serverStatusManager, filterLoader, sessionCtxDecorator, usageNotifier, reqCompleteHandler, registry, directMemoryMonitor, eventLoopGroupMetrics, discoveryClient, applicationInfoManager, accessLogPublisher); } @Override protected Map choosePortsAndChannels(ChannelGroup clientChannels) { Map portsToChannels = new HashMap<>(); int port = new DynamicIntProperty("zuul.server.port.main", 8085).get(); String mainPortName = "main"; ChannelConfig channelConfig = BaseServerStartup.defaultChannelConfig(mainPortName); ServerSslConfig sslConfig; /* These settings may need to be tweaked depending if you're running behind an ELB HTTP listener, TCP listener, * or directly on the internet. */ ChannelConfig channelDependencies = defaultChannelDependencies(mainPortName); channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.ALWAYS); channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, false); channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false); channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, false); portsToChannels.put(port, new ZuulServerChannelInitializer(port, channelConfig, channelDependencies, clientChannels)); logPortConfigured(port, null); return portsToChannels; } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulBootstrap.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import java.io.IOException; import com.google.inject.Injector; import com.google.inject.Scopes; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.providers.MyDataCenterInstanceConfigProvider; import com.netflix.config.ConfigurationManager; import com.netflix.governator.InjectorBuilder; import com.netflix.zuul.netty.server.BaseServerStartup; import com.netflix.zuul.netty.server.Server; /** *

          The Zuul 2.x demo with Sentinel gateway flow control.

          *

          Run with {@code -Dcsp.sentinel.api.type=1} to mark the demo as API gateway.

          * * @author wavesZh */ public class ZuulBootstrap { public static void main(String[] args) { new ZuulBootstrap().start(); } public void start() { Server server; try { // Load sample rules. You may also manage rules in Sentinel dashboard. new GatewayRuleConfig().doInit(); ConfigurationManager.loadCascadedPropertiesFromResources("application"); Injector injector = InjectorBuilder.fromModule(new ZuulModule()).createInjector(); injector.getInstance(FiltersRegisteringService.class); BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class); server = serverStartup.server(); server.start(true); } catch (IOException e) { e.printStackTrace(); } } public static class ZuulModule extends ZuulSampleModule { @Override protected void configure() { //DataCenterInfo bind(EurekaInstanceConfig.class) .toProvider(MyDataCenterInstanceConfigProvider.class) .in(Scopes.SINGLETON); super.configure(); } } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulClasspathFiltersModule.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound.SentinelZuulInboundFilter; import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound.SentinelZuulOutboundFilter; import com.alibaba.csp.sentinel.demo.zuul2.gateway.filters.Route; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import com.netflix.zuul.BasicFilterUsageNotifier; import com.netflix.zuul.DynamicCodeCompiler; import com.netflix.zuul.FilterFactory; import com.netflix.zuul.FilterUsageNotifier; import com.netflix.zuul.filters.ZuulFilter; import com.netflix.zuul.groovy.GroovyCompiler; import com.netflix.zuul.guice.GuiceFilterFactory; public class ZuulClasspathFiltersModule extends AbstractModule { @Override protected void configure() { bind(DynamicCodeCompiler.class).to(GroovyCompiler.class); bind(FilterFactory.class).to(GuiceFilterFactory.class); bind(FilterUsageNotifier.class).to(BasicFilterUsageNotifier.class); Multibinder filterMultibinder = Multibinder.newSetBinder(binder(), ZuulFilter.class); filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500)); filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500)); filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint()); filterMultibinder.addBinding().toInstance(new Route()); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/ZuulSampleModule.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway; import com.google.inject.AbstractModule; import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; import com.netflix.discovery.DiscoveryClient; import com.netflix.netty.common.accesslog.AccessLogPublisher; import com.netflix.netty.common.status.ServerStatusManager; import com.netflix.spectator.api.DefaultRegistry; import com.netflix.spectator.api.Registry; import com.netflix.zuul.BasicRequestCompleteHandler; import com.netflix.zuul.FilterFileManager; import com.netflix.zuul.RequestCompleteHandler; import com.netflix.zuul.context.SessionContextDecorator; import com.netflix.zuul.context.ZuulSessionContextDecorator; import com.netflix.zuul.init.ZuulFiltersModule; import com.netflix.zuul.netty.server.BaseServerStartup; import com.netflix.zuul.netty.server.ClientRequestReceiver; import com.netflix.zuul.origins.BasicNettyOriginManager; import com.netflix.zuul.origins.OriginManager; import com.netflix.zuul.stats.BasicRequestMetricsPublisher; import com.netflix.zuul.stats.RequestMetricsPublisher; /** * Zuul Sample Module * * Author: Arthur Gonigberg * Date: November 20, 2017 */ public class ZuulSampleModule extends AbstractModule { @Override protected void configure() { // sample specific bindings bind(BaseServerStartup.class).to(SampleServerStartup.class); // use provided basic netty origin manager bind(OriginManager.class).to(BasicNettyOriginManager.class); // zuul filter loading install(new ZuulFiltersModule()); bind(FilterFileManager.class).asEagerSingleton(); install(new ZuulClasspathFiltersModule()); // general server bindings // health/discovery status bind(ServerStatusManager.class); // decorate new sessions when requests come in bind(SessionContextDecorator.class).to(ZuulSessionContextDecorator.class); // atlas metrics registry bind(Registry.class).to(DefaultRegistry.class); // metrics post-request completion bind(RequestCompleteHandler.class).to(BasicRequestCompleteHandler.class); // discovery client bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClient.DiscoveryClientOptionalArgs.class); // timings publisher bind(RequestMetricsPublisher.class).to(BasicRequestMetricsPublisher.class); // access logger, including request ID generator bind(AccessLogPublisher.class).toInstance(new AccessLogPublisher("ACCESS", (channel, httpRequest) -> ClientRequestReceiver.getRequestFromChannel(channel).getContext().getUUID())); } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/NotFoundEndpoint.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway.filters; import com.netflix.zuul.filters.http.HttpSyncEndpoint; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.message.http.HttpResponseMessageImpl; import org.apache.http.HttpStatus; public class NotFoundEndpoint extends HttpSyncEndpoint { @Override public HttpResponseMessage apply(HttpRequestMessage request) { HttpResponseMessage response = new HttpResponseMessageImpl(request.getContext(), request, HttpStatus.SC_NOT_FOUND); response.finishBufferedBodyIfIncomplete(); return response; } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/java/com/alibaba/csp/sentinel/demo/zuul2/gateway/filters/Route.java ================================================ package com.alibaba.csp.sentinel.demo.zuul2.gateway.filters; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.filters.http.HttpInboundSyncFilter; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.netty.filter.ZuulEndPointRunner; public class Route extends HttpInboundSyncFilter { @Override public HttpRequestMessage apply(HttpRequestMessage request) { SessionContext context = request.getContext(); switch (request.getPath()) { case "/images": context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); context.setRouteVIP("images"); break; case "/comments": context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); context.setRouteVIP("comments"); break; default: context.setEndpoint(NotFoundEndpoint.class.getCanonicalName()); } return request; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter(HttpRequestMessage msg) { return true; } } ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/application.properties ================================================ zuul.server.port.main=8887 # Deactivate Eureka eureka.registration.enabled = false eureka.preferSameZone = false eureka.shouldUseDns = false eureka.shouldFetchRegistry=false # Loading Filters zuul.filters.packages = com.netflix.zuul.filters.common,com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint,com.alibaba.csp.sentinel.demo.zuul2.gateway.filters # Routing to proxied back-end services comments.ribbon.listOfServers=localhost:8081 comments.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList images.ribbon.listOfServers=localhost:8082 images.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList ================================================ FILE: sentinel-demo/sentinel-demo-zuul2-gateway/src/main/resources/log4j.properties ================================================ log4j.rootLogger=INFO,stdout # stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=com.netflix.zuul.logging.FilteredPatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c [%t] %m%n ================================================ FILE: sentinel-extension/README.md ================================================ # Sentinel Extension Sentinel extension modules provide additional extension points and functions. ================================================ FILE: sentinel-extension/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-extension pom sentinel-datasource-extension sentinel-datasource-nacos sentinel-datasource-zookeeper sentinel-datasource-apollo sentinel-datasource-redis sentinel-annotation-aspectj sentinel-parameter-flow-control sentinel-datasource-spring-cloud-config sentinel-datasource-consul sentinel-datasource-etcd sentinel-datasource-eureka sentinel-annotation-cdi-interceptor sentinel-metric-exporter sentinel-prometheus-metric-exporter ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/README.md ================================================ # Sentinel Annotation AspectJ This extension is an AOP implementation using AspectJ for Sentinel annotations. Currently only runtime weaving is supported. ## Annotation The `@SentinelResource` annotation indicates a resource definition, including: - `value`: Resource name, required (cannot be empty) - `entryType`: Resource entry type (inbound or outbound), `EntryType.OUT` by default - `fallback` (refactored since 1.6.0): Fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). The fallback method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The method signature requirement: - The return type should match the origin method; - The parameter list should match the origin method, and an additional `Throwable` parameter can be provided to get the actual exception. - `defaultFallback` (since 1.6.0): The default fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). Its intended to be a universal common fallback method. The method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The default fallback method signature requirement: - The return type should match the origin method; - parameter list should be empty, and an additional `Throwable` parameter can be provided to get the actual exception. - `blockHandler`: Handler method that handles `BlockException` when blocked. The parameter list of the method should match original method, with the last additional parameter type `BlockException`. The return type should be same as the original method. The `blockHandler` method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `blockHandlerClass` with corresponding `Class` (Note the method in other classes must be *static*). - `exceptionsToIgnore` (since 1.6.0): List of business exception classes that should not be traced and caught in fallback. - `exceptionsToTrace` (since 1.5.1): List of business exception classes to trace and record. In most cases, using `exceptionsToIgnore` is better. If both `exceptionsToTrace` and `exceptionsToIgnore` are present, only `exceptionsToIgnore` will be activated. For example: ```java @SentinelResource(value = "abc", fallback = "doFallback") public String doSomething(long i) { return "Hello " + i; } public String doFallback(long i, Throwable t) { // Return fallback value. return "fallback"; } public String defaultFallback(Throwable t) { return "default_fallback"; } ``` ## Configuration ### AspectJ If you are using AspectJ directly, you can add the Sentinel annotation aspect to your `aop.xml`: ```xml ``` ### Spring AOP If you are using Spring AOP, you should add a configuration to register the aspect as a Spring bean: ```java @Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } } ``` An example for using Sentinel Annotation AspectJ with Spring Boot can be found in [sentinel-demo/sentinel-demo-annotation-spring-aop](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-annotation-spring-aop). ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-annotation-aspectj jar 1.9.2 5.2.21.RELEASE com.alibaba.csp sentinel-core org.aspectj aspectjweaver ${aspectj.version} junit junit test org.assertj assertj-core test org.mockito mockito-core test org.springframework spring-context ${spring.test.version} test org.springframework spring-aop ${spring.test.version} test org.springframework spring-test ${spring.test.version} test ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/AbstractSentinelAspectSupport.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.MethodUtil; import com.alibaba.csp.sentinel.util.StringUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; /** * Some common functions for Sentinel annotation aspect. * * @author Eric Zhao * @author zhaoyuguang * @author dowenliu-xyz(hawkdowen@hotmail.com) */ public abstract class AbstractSentinelAspectSupport { protected void traceException(Throwable ex) { Tracer.trace(ex); } protected void traceException(Throwable ex, SentinelResource annotation) { Class[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { return; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); } } /** * Check whether the exception is in provided list of exception classes. * * @param ex provided throwable * @param exceptions list of exceptions * @return true if it is in the list, otherwise false */ protected boolean exceptionBelongsTo(Throwable ex, Class[] exceptions) { if (exceptions == null) { return false; } for (Class exceptionClass : exceptions) { if (exceptionClass.isAssignableFrom(ex.getClass())) { return true; } } return false; } protected String getResourceName(String resourceName, /*@NonNull*/ Method method) { // If resource name is present in annotation, use this value. if (StringUtil.isNotBlank(resourceName)) { return resourceName; } // Parse name of target method. return MethodUtil.resolveMethodName(method); } protected Object handleFallback(ProceedingJoinPoint pjp, SentinelResource annotation, Throwable ex) throws Throwable { return handleFallback(pjp, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex); } protected Object handleFallback(ProceedingJoinPoint pjp, String fallback, String defaultFallback, Class[] fallbackClass, Throwable ex) throws Throwable { Object[] originArgs = pjp.getArgs(); // Execute fallback function if configured. Method fallbackMethod = extractFallbackMethod(pjp, fallback, fallbackClass); if (fallbackMethod != null) { // Construct args. int paramCount = fallbackMethod.getParameterTypes().length; Object[] args; if (paramCount == originArgs.length) { args = originArgs; } else { args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; } return invoke(pjp, fallbackMethod, args); } // If fallback is absent, we'll try the defaultFallback if provided. return handleDefaultFallback(pjp, defaultFallback, fallbackClass, ex); } protected Object handleDefaultFallback(ProceedingJoinPoint pjp, String defaultFallback, Class[] fallbackClass, Throwable ex) throws Throwable { // Execute the default fallback function if configured. Method fallbackMethod = extractDefaultFallbackMethod(pjp, defaultFallback, fallbackClass); if (fallbackMethod != null) { // Construct args. Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[] {ex}; return invoke(pjp, fallbackMethod, args); } // If no any fallback is present, then directly throw the exception. throw ex; } protected Object handleBlockException(ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex) throws Throwable { // Execute block handler if configured. Method blockHandlerMethod = extractBlockHandlerMethod(pjp, annotation.blockHandler(), annotation.blockHandlerClass()); if (blockHandlerMethod != null) { Object[] originArgs = pjp.getArgs(); // Construct args. Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; return invoke(pjp, blockHandlerMethod, args); } // If no block handler is present, then go to fallback. return handleFallback(pjp, annotation, ex); } private Object invoke(ProceedingJoinPoint pjp, Method method, Object[] args) throws Throwable { try { if (!method.isAccessible()) { makeAccessible(method); } if (isStatic(method)) { return method.invoke(null, args); } return method.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { // throw the actual exception throw e.getTargetException(); } } /** * Make the given method accessible, explicitly setting it accessible if * necessary. The {@code setAccessible(true)} method is only called * when actually necessary, to avoid unnecessary conflicts with a JVM * SecurityManager (if active). * @param method the method to make accessible * @see java.lang.reflect.Method#setAccessible */ private static void makeAccessible(Method method) { boolean isNotPublic = !Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()); if (isNotPublic && !method.isAccessible()) { method.setAccessible(true); } } private Method extractFallbackMethod(ProceedingJoinPoint pjp, String fallbackName, Class[] locationClass) { if (StringUtil.isBlank(fallbackName)) { return null; } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz = mustStatic ? locationClass[0] : pjp.getTarget().getClass(); Method originMethod = resolveMethod(pjp); MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName, originMethod.getParameterTypes()); if (m == null) { // First time, resolve the fallback. Method method = resolveFallbackInternal(originMethod, fallbackName, clazz, mustStatic); // Cache the method instance. ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, originMethod.getParameterTypes(), method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method extractDefaultFallbackMethod(ProceedingJoinPoint pjp, String defaultFallback, Class[] locationClass) { if (StringUtil.isBlank(defaultFallback)) { SentinelResource annotationClass = pjp.getTarget().getClass().getAnnotation(SentinelResource.class); if (annotationClass != null && StringUtil.isNotBlank(annotationClass.defaultFallback())) { defaultFallback = annotationClass.defaultFallback(); if (locationClass == null || locationClass.length < 1) { locationClass = annotationClass.fallbackClass(); } } else { return null; } } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz = mustStatic ? locationClass[0] : pjp.getTarget().getClass(); MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback); if (m == null) { // First time, resolve the default fallback. Class originReturnType = resolveMethod(pjp).getReturnType(); // Default fallback allows two kinds of parameter list. // One is empty parameter list. Class[] defaultParamTypes = new Class[0]; // The other is a single parameter {@link Throwable} to get relevant exception info. Class[] paramTypeWithException = new Class[] {Throwable.class}; // We first find the default fallback with empty parameter list. Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes); // If default fallback with empty params is absent, we then try to find the other one. if (method == null) { method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException); } // Cache the method instance. ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method resolveFallbackInternal(Method originMethod, String name, Class clazz, boolean mustStatic) { // Fallback function allows two kinds of parameter list. Class[] defaultParamTypes = originMethod.getParameterTypes(); Class[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1); paramTypesWithException[paramTypesWithException.length - 1] = Throwable.class; // We first find the fallback matching the signature of origin method. Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes); // If fallback matching the origin method is absent, we then try to find the other one. if (method == null) { method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException); } return method; } private Method extractBlockHandlerMethod(ProceedingJoinPoint pjp, String name, Class[] locationClass) { if (StringUtil.isBlank(name)) { return null; } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz; if (mustStatic) { clazz = locationClass[0]; } else { // By default current class. clazz = pjp.getTarget().getClass(); } Method originMethod = resolveMethod(pjp); MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name, originMethod.getParameterTypes()); if (m == null) { // First time, resolve the block handler. Method method = resolveBlockHandlerInternal(originMethod, name, clazz, mustStatic); // Cache the method instance. ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, originMethod.getParameterTypes(), method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method resolveBlockHandlerInternal(Method originMethod, String name, Class clazz, boolean mustStatic) { Class[] originList = originMethod.getParameterTypes(); Class[] parameterTypes = Arrays.copyOf(originList, originList.length + 1); parameterTypes[parameterTypes.length - 1] = BlockException.class; return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes); } private boolean checkStatic(boolean mustStatic, Method method) { return !mustStatic || isStatic(method); } private Method findMethod(boolean mustStatic, Class clazz, String name, Class returnType, Class... parameterTypes) { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (name.equals(method.getName()) && checkStatic(mustStatic, method) && returnType.isAssignableFrom(method.getReturnType()) && Arrays.equals(parameterTypes, method.getParameterTypes())) { RecordLog.info("Resolved method [{}] in class [{}]", name, clazz.getCanonicalName()); return method; } } // Current class not found, find in the super classes recursively. Class superClass = clazz.getSuperclass(); if (superClass != null && !Object.class.equals(superClass)) { return findMethod(mustStatic, superClass, name, returnType, parameterTypes); } else { String methodType = mustStatic ? " static" : ""; RecordLog.warn("Cannot find{} method [{}] in class [{}] with parameters {}", methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes)); return null; } } private boolean isStatic(Method method) { return Modifier.isStatic(method.getModifiers()); } protected Method resolveMethod(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Class targetClass = joinPoint.getTarget().getClass(); Method method = getDeclaredMethodFor(targetClass, signature.getName(), signature.getMethod().getParameterTypes()); if (method == null) { throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName()); } return method; } /** * Get declared method with provided name and parameterTypes in given class and its super classes. * All parameters should be valid. * * @param clazz class where the method is located * @param name method name * @param parameterTypes method parameter type list * @return resolved method, null if not found */ private Method getDeclaredMethodFor(Class clazz, String name, Class... parameterTypes) { try { return clazz.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException e) { Class superClass = clazz.getSuperclass(); if (superClass != null) { return getDeclaredMethodFor(superClass, name, parameterTypes); } } return null; } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/MethodWrapper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import java.lang.reflect.Method; /** * @author Eric Zhao */ class MethodWrapper { private final Method method; private final boolean present; private MethodWrapper(Method method, boolean present) { this.method = method; this.present = present; } static MethodWrapper wrap(Method method) { if (method == null) { return none(); } return new MethodWrapper(method, true); } static MethodWrapper none() { return new MethodWrapper(null, false); } Method getMethod() { return method; } boolean isPresent() { return present; } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.util.StringUtil; /** * Registry for resource configuration metadata (e.g. fallback method) * * @author Eric Zhao * @author dowenliu-xyz(hawkdowen@hotmail.com) */ final class ResourceMetadataRegistry { private static final Map FALLBACK_MAP = new ConcurrentHashMap<>(); private static final Map DEFAULT_FALLBACK_MAP = new ConcurrentHashMap<>(); private static final Map BLOCK_HANDLER_MAP = new ConcurrentHashMap<>(); /** * @deprecated use {@link #lookupFallback(Class, String, Class[])} */ @Deprecated static MethodWrapper lookupFallback(Class clazz, String name) { return FALLBACK_MAP.get(getKey(clazz, name)); } static MethodWrapper lookupFallback(Class clazz, String name, Class[] parameterTypes) { return FALLBACK_MAP.get(getKey(clazz, name, parameterTypes)); } static MethodWrapper lookupDefaultFallback(Class clazz, String name) { return DEFAULT_FALLBACK_MAP.get(getKey(clazz, name)); } /** * @deprecated use {@link #lookupBlockHandler(Class, String, Class[])} */ @Deprecated static MethodWrapper lookupBlockHandler(Class clazz, String name) { return BLOCK_HANDLER_MAP.get(getKey(clazz, name)); } static MethodWrapper lookupBlockHandler(Class clazz, String name, Class[] parameterTypes) { return BLOCK_HANDLER_MAP.get(getKey(clazz, name, parameterTypes)); } /** * @deprecated use {@link #updateFallbackFor(Class, String, Class[], Method)} */ @Deprecated static void updateFallbackFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } static void updateFallbackFor(Class clazz, String handlerName, Class[] parameterTypes, Method handlerMethod) { if (clazz == null || StringUtil.isBlank(handlerName)) { throw new IllegalArgumentException("Bad argument"); } FALLBACK_MAP.put(getKey(clazz, handlerName, parameterTypes), MethodWrapper.wrap(handlerMethod)); } static void updateDefaultFallbackFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } DEFAULT_FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } /** * @deprecated use {@link #updateBlockHandlerFor(Class, String, Class[], Method)} */ @Deprecated static void updateBlockHandlerFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } BLOCK_HANDLER_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } static void updateBlockHandlerFor(Class clazz, String handlerName, Class[] parameterTypes, Method handlerMethod) { if (clazz == null || StringUtil.isBlank(handlerName)) { throw new IllegalArgumentException("Bad argument"); } BLOCK_HANDLER_MAP.put(getKey(clazz, handlerName, parameterTypes), MethodWrapper.wrap(handlerMethod)); } private static String getKey(Class clazz, String name) { return String.format("%s:%s", clazz.getCanonicalName(), name); } private static String getKey(Class clazz, String name, Class[] parameterTypes) { return String.format( "%s:%s;%s", clazz.getCanonicalName(), name, Arrays.stream(parameterTypes).map(Class::getCanonicalName).collect(Collectors.joining(",")) ); } /** * Only for internal test. */ static void clearFallbackMap() { FALLBACK_MAP.clear(); } /** * Only for internal test. */ static void clearBlockHandlerMap() { BLOCK_HANDLER_MAP.clear(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import java.lang.reflect.Method; /** * Aspect for methods with {@link SentinelResource} annotation. * * @author Eric Zhao */ @Aspect public class SentinelResourceAspect extends AbstractSentinelAspectSupport { @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable { Method originMethod = resolveMethod(pjp); SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class); if (annotation == null) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } String resourceName = getResourceName(annotation.value(), originMethod); EntryType entryType = annotation.entryType(); int resourceType = annotation.resourceType(); Entry entry = null; try { entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs()); return pjp.proceed(); } catch (BlockException ex) { return handleBlockException(pjp, annotation, ex); } catch (Throwable ex) { Class[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); } // No fallback function can handle the exception, so throw it out. throw ex; } finally { if (entry != null) { entry.exit(1, pjp.getArgs()); } } } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/AbstractSentinelAspectSupportTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooService; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.*; /** * @author Eric Zhao */ public class AbstractSentinelAspectSupportTest extends AbstractSentinelAspectSupport { @Test public void testGetResourceName() throws Exception { Method method = FooService.class.getMethod("random"); String resourceName = "someRandom"; String expectedResolvedName = FooService.class.getName() + ":random()"; assertThat(getResourceName(resourceName, method)).isEqualTo(resourceName); assertThat(getResourceName(null, method)).isEqualTo(expectedResolvedName); assertThat(getResourceName("", method)).isEqualTo(expectedResolvedName); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/MethodWrapperTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class MethodWrapperTest { @Test public void testWrapMethod() { Method method = String.class.getMethods()[0]; MethodWrapper m = MethodWrapper.wrap(method); assertThat(m.isPresent()).isTrue(); assertThat(m.getMethod()).isSameAs(method); } @Test public void testNone() { MethodWrapper none = MethodWrapper.none(); assertThat(none.isPresent()).isFalse(); assertThat(none.getMethod()).isNull(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistryTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj; import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooService; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao * @author dowenliu-xyz(hawkdowen@hotmail.com) */ public class ResourceMetadataRegistryTest { @Before public void setUp() throws Exception { ResourceMetadataRegistry.clearBlockHandlerMap(); ResourceMetadataRegistry.clearFallbackMap(); } @After public void tearDown() throws Exception { ResourceMetadataRegistry.clearBlockHandlerMap(); ResourceMetadataRegistry.clearFallbackMap(); } @Test public void testUpdateThenLookupFallback() { Class clazz = FooService.class; String methodName = "someMethodFallback"; Class[] parameterTypes = new Class[]{String.class, int.class}; Method method = clazz.getMethods()[0]; assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes)).isNull(); ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, parameterTypes, null); assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes).isPresent()).isFalse(); ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, parameterTypes, method); MethodWrapper wrapper = ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes); assertThat(wrapper.isPresent()).isTrue(); assertThat(wrapper.getMethod()).isSameAs(method); } @Test public void testUpdateThenLookupBlockHandler() { Class clazz = FooService.class; String methodName = "someMethodBlockHand;er"; Class[] parameterTypes = new Class[]{String.class, int.class}; Method method = clazz.getMethods()[1]; assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes)).isNull(); ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, parameterTypes, null); assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes).isPresent()).isFalse(); ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, parameterTypes, method); MethodWrapper wrapper = ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes); assertThat(wrapper.isPresent()).isTrue(); assertThat(wrapper.getMethod()).isSameAs(method); } @Test(expected = IllegalArgumentException.class) public void testUpdateBlockHandlerBadArgument() { ResourceMetadataRegistry.updateBlockHandlerFor(null, "sxs", new Class[]{}, String.class.getMethods()[0]); } @Test(expected = IllegalArgumentException.class) public void testUpdateFallbackBadArgument() { ResourceMetadataRegistry.updateBlockHandlerFor(String.class, "", new Class[0], String.class.getMethods()[0]); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/SentinelAnnotationIntegrationTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration; import com.alibaba.csp.sentinel.annotation.aspectj.integration.config.AopTestConfig; import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.BarService; import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooService; import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.MethodUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Collections; import static org.assertj.core.api.Assertions.*; /** * Integration test for Sentinel annotation AspectJ extension. * * @author Eric Zhao * @author zhaoyuguang */ @ContextConfiguration(classes = {SentinelAnnotationIntegrationTest.class, AopTestConfig.class}) public class SentinelAnnotationIntegrationTest extends AbstractJUnit4SpringContextTests { @Autowired private FooService fooService; @Autowired private BarService barService; @Test public void testProxySuccessful() { assertThat(AopUtils.isAopProxy(fooService)).isTrue(); assertThat(AopUtils.isCglibProxy(fooService)).isTrue(); } @Test public void testForeignBlockHandlerClass() throws Exception { assertThat(fooService.random()).isNotEqualTo(FooUtil.BLOCK_FLAG); String resourceName = MethodUtil.resolveMethodName(FooService.class.getDeclaredMethod("random")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.random()).isEqualTo(FooUtil.BLOCK_FLAG); assertThat(cn.blockQps()).isPositive(); } @Test(expected = UndeclaredThrowableException.class) public void testBlockHandlerNotFound() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); fooService.baz("Sentinel"); } @Test public void testAnnotationExceptionsToIgnore() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); try { fooService.baz("fail"); fail("should not reach here"); } catch (IllegalMonitorStateException ex) { assertThat(cn.exceptionQps()).isZero(); } } @Test public void testFallbackWithNoParams() throws Exception { assertThat(fooService.fooWithFallback(1)).isEqualTo("Hello for 1"); String resourceName = "apiFooWithFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Fallback should be ignored for this. try { fooService.fooWithFallback(5758); fail("should not reach here"); } catch (IllegalAccessException e) { assertThat(cn.exceptionQps()).isZero(); } // Fallback should take effect. assertThat(fooService.fooWithFallback(5763)).isEqualTo("eee..."); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Fallback should not take effect for BlockException, as blockHandler is configured. assertThat(fooService.fooWithFallback(2221)).isEqualTo("Oops, 2221"); assertThat(cn.blockQps()).isPositive(); } @Test public void testDefaultFallbackWithSingleParam() { assertThat(fooService.anotherFoo(1)).isEqualTo("Hello for 1"); String resourceName = "apiAnotherFooWithDefaultFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Default fallback should take effect. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Default fallback should also take effect for BlockException. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.blockQps()).isPositive(); } @Test public void testNormalBlockHandlerAndFallback() throws Exception { assertThat(fooService.foo(1)).isEqualTo("Hello for 1"); String resourceName = "apiFoo"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Test for biz exception. try { fooService.foo(5758); fail("should not reach here"); } catch (Exception ex) { // Should not be traced. assertThat(cn.exceptionQps()).isZero(); } try { fooService.foo(5763); fail("should not reach here"); } catch (Exception ex) { assertThat(cn.exceptionQps()).isPositive(); } // Test for blockHandler FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.foo(1121)).isEqualTo("Oops, 1121"); assertThat(cn.blockQps()).isPositive(); } @Test public void testClassLevelDefaultFallbackWithSingleParam() { assertThat(barService.anotherBar(1)).isEqualTo("Hello for 1"); String resourceName = "apiAnotherBarWithDefaultFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); assertThat(barService.doSomething(1)).isEqualTo("do something"); String resourceName1 = "com.alibaba.csp.sentinel.annotation.aspectj.integration.service.BarService:doSomething(int)"; ClusterNode cn1 = ClusterBuilderSlot.getClusterNode(resourceName1); assertThat(cn1).isNotNull(); assertThat(cn1.passQps()).isPositive(); assertThat(barService.anotherBar(5758)).isEqualTo("eee..."); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); assertThat(barService.doSomething(5758)).isEqualTo("GlobalFallback:doFallback"); assertThat(cn1.exceptionQps()).isPositive(); assertThat(cn1.blockQps()).isZero(); } @Test public void testFallBackPrivateMethod() throws Exception { String resourceName = "apiFooWithFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); try { fooService.fooWithPrivateFallback(5758); fail("should not reach here"); } catch (Exception ex) { // Should not be traced. assertThat(cn.exceptionQps()).isZero(); } assertThat(fooService.fooWithPrivateFallback(5763)).isEqualTo("EEE..."); // Test for blockHandler FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.fooWithPrivateFallback(2221)).isEqualTo("Oops, 2221"); } @Before public void setUp() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); } @After public void tearDown() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/config/AopTestConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration.config; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @author Eric Zhao */ @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) @ComponentScan("com.alibaba.csp.sentinel.annotation.aspectj.integration") public class AopTestConfig { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/BarService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration.service; import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.stereotype.Service; /** * @author zhaoyuguang */ @Service @SentinelResource(defaultFallback = "doFallback", fallbackClass = GlobalFallback.class) public class BarService { @SentinelResource(value = "apiAnotherBarWithDefaultFallback", defaultFallback = "fallbackFunc") public String anotherBar(int i) { if (i == 5758) { throw new IllegalArgumentException("oops"); } return "Hello for " + i; } @SentinelResource() public String doSomething(int i) { if (i == 5758) { throw new IllegalArgumentException("oops"); } return "do something"; } public String fallbackFunc(Throwable t) { System.out.println(t.getMessage()); return "eee..."; } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooService.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration.service; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import org.springframework.stereotype.Service; import java.util.concurrent.ThreadLocalRandom; /** * @author Eric Zhao */ @Service public class FooService { @SentinelResource(value = "apiFoo", blockHandler = "fooBlockHandler", exceptionsToTrace = {IllegalArgumentException.class}) public String foo(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResource(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc", exceptionsToTrace = {IllegalArgumentException.class}) public String fooWithFallback(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResource(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback", fallbackClass = {FooUtil.class}) public String anotherFoo(int i) { if (i == 5758) { throw new IllegalArgumentException("oops"); } return "Hello for " + i; } @SentinelResource(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class) public int random() { return ThreadLocalRandom.current().nextInt(0, 30000); } @SentinelResource(value = "apiBaz", blockHandler = "bazBlockHandler", exceptionsToIgnore = {IllegalMonitorStateException.class}) public String baz(String name) { if (name.equals("fail")) { throw new IllegalMonitorStateException("boom!"); } return "cheers, " + name; } @SentinelResource(value = "apiFooWithFallback", blockHandler = "fooBlockHandlerPrivate", fallback = "fooFallbackFuncPrivate", exceptionsToTrace = {IllegalArgumentException.class}) public String fooWithPrivateFallback(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } public String fooBlockHandler(int i, BlockException ex) { return "Oops, " + i; } public String fooFallbackFunc(int i) { return "eee..."; } private String fooFallbackFuncPrivate(int i) { return "EEE..."; } private String fooBlockHandlerPrivate(int i, BlockException ex) { return "Oops, " + i; } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration.service; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Eric Zhao */ public class FooUtil { public static final int BLOCK_FLAG = 88888; public static final String FALLBACK_DEFAULT_RESULT = "fallback"; public static int globalBlockHandler(BlockException ex) { System.out.println("Oops: " + ex.getClass().getSimpleName()); return BLOCK_FLAG; } public static String globalDefaultFallback(Throwable t) { System.out.println("Fallback caught: " + t.getClass().getSimpleName()); return FALLBACK_DEFAULT_RESULT; } } ================================================ FILE: sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/GlobalFallback.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.aspectj.integration.service; /** * @author zhaoyuguang */ public class GlobalFallback { public static String doFallback(Throwable t) { return "GlobalFallback:doFallback"; } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/README.md ================================================ # Sentinel Annotation CDI extension This extension is an implementation using CDI interceptor for Sentinel annotations. [JSR 318: Enterprise JavaBeansTM 3.1/Interceptors 1.2](https://jcp.org/en/jsr/detail?id=318) define the javax interceptor and [CDI](http://www.cdi-spec.org/) related specifications extends the Java Interceptors specification and allows interceptor bindings to be applied to CDI stereotypes. [CDI](http://www.cdi-spec.org/) is an abbreviation for Contexts and Dependency Injection, the related JSRs are : [JSR 365: Contexts and Dependency Injection for JavaTM 2.0](https://jcp.org/en/jsr/detail?id=365), [JSR 346: Contexts and Dependency Injection for JavaTM EE 1.1](https://jcp.org/en/jsr/detail?id=346), [JSR 299: Contexts and Dependency Injection for the JavaTM EE platform](https://jcp.org/en/jsr/detail?id=299) ## Annotation The `@SentinelResourceBinding` is modified from `@SentinelResource` by adding `@InterceptorBinding` for CDI specification, and in order to intercept all kinds of `@SentinelResourceBinding` with different attributes in one interceptor, all attributes of `@SentinelResourceBinding` are annotated with `@Nonbinding`. The `@SentinelResourceBinding` annotation indicates a resource definition, including: - `value`: Resource name, required (cannot be empty) - `entryType`: Traffic type (inbound or outbound), `EntryType.OUT` by default - `fallback`: Fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). The fallback method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The method signature requirement: - The return type should match the origin method; - The parameter list should match the origin method, and an additional `Throwable` parameter can be provided to get the actual exception. - `defaultFallback`: The default fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). Its intended to be a universal common fallback method. The method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The default fallback method signature requirement: - The return type should match the origin method; - parameter list should be empty, and an additional `Throwable` parameter can be provided to get the actual exception. - `blockHandler`: Handler method that handles `BlockException` when blocked. The parameter list of the method should match original method, with the last additional parameter type `BlockException`. The return type should be same as the original method. The `blockHandler` method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `blockHandlerClass` with corresponding `Class` (Note the method in other classes must be *static*). - `exceptionsToIgnore`: List of business exception classes that should not be traced and caught in fallback. - `exceptionsToTrace`: List of business exception classes to trace and record. In most cases, using `exceptionsToIgnore` is better. If both `exceptionsToTrace` and `exceptionsToIgnore` are present, only `exceptionsToIgnore` will be activated. For example: ```java @SentinelResourceBinding(value = "abc", fallback = "doFallback") public String doSomething(long i) { return "Hello " + i; } public String doFallback(long i, Throwable t) { // Return fallback value. return "fallback"; } public String defaultFallback(Throwable t) { return "default_fallback"; } ``` ## Configuration According to [9.4. Interceptor enablement and ordering](https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#enabled_interceptors), to enable the interceptor, we may add configuration in `resources/META-INF/beans.xml` like this: ```xml com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor ``` ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-annotation-cdi-interceptor jar 1.2.5 2.0.2 jakarta.interceptor jakarta.interceptor-api ${jakarta.interceptor-api.version} jakarta.enterprise jakarta.enterprise.cdi-api ${jakarta.enterprise.cdi-api.version} com.alibaba.csp sentinel-core junit junit test org.assertj assertj-core test org.jboss.weld.se weld-se-shaded 3.1.4.Final test ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupport.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.MethodUtil; import com.alibaba.csp.sentinel.util.StringUtil; import javax.interceptor.InvocationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; /** * Some common functions for Sentinel annotation CDI extension. * * @author Eric Zhao * @author seasidesky * @author dowenliu-xyz(hawkdowen@hotmail.com) */ public abstract class AbstractSentinelInterceptorSupport { protected void traceException(Throwable ex) { Tracer.trace(ex); } protected void traceException(Throwable ex, SentinelResourceBinding annotation) { Class[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { return; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); } } /** * Check whether the exception is in provided list of exception classes. * * @param ex provided throwable * @param exceptions list of exceptions * @return true if it is in the list, otherwise false */ protected boolean exceptionBelongsTo(Throwable ex, Class[] exceptions) { if (exceptions == null) { return false; } for (Class exceptionClass : exceptions) { if (exceptionClass.isAssignableFrom(ex.getClass())) { return true; } } return false; } protected String getResourceName(String resourceName, /*@NonNull*/ Method method) { // If resource name is present in annotation, use this value. if (StringUtil.isNotBlank(resourceName)) { return resourceName; } // Parse name of target method. return MethodUtil.resolveMethodName(method); } protected Object handleFallback(InvocationContext ctx, SentinelResourceBinding annotation, Throwable ex) throws Throwable { return handleFallback(ctx, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex); } protected Object handleFallback(InvocationContext ctx, String fallback, String defaultFallback, Class[] fallbackClass, Throwable ex) throws Throwable { Object[] originArgs = ctx.getParameters(); // Execute fallback function if configured. Method fallbackMethod = extractFallbackMethod(ctx, fallback, fallbackClass); if (fallbackMethod != null) { // Construct args. int paramCount = fallbackMethod.getParameterTypes().length; Object[] args; if (paramCount == originArgs.length) { args = originArgs; } else { args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; } try { if (isStatic(fallbackMethod)) { return fallbackMethod.invoke(null, args); } return fallbackMethod.invoke(ctx.getTarget(), args); } catch (InvocationTargetException e) { // throw the actual exception throw e.getTargetException(); } } // If fallback is absent, we'll try the defaultFallback if provided. return handleDefaultFallback(ctx, defaultFallback, fallbackClass, ex); } protected Object handleDefaultFallback(InvocationContext ctx, String defaultFallback, Class[] fallbackClass, Throwable ex) throws Throwable { // Execute the default fallback function if configured. Method fallbackMethod = extractDefaultFallbackMethod(ctx, defaultFallback, fallbackClass); if (fallbackMethod != null) { // Construct args. Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[] {ex}; try { if (isStatic(fallbackMethod)) { return fallbackMethod.invoke(null, args); } return fallbackMethod.invoke(ctx.getTarget(), args); } catch (InvocationTargetException e) { // throw the actual exception throw e.getTargetException(); } } // If no any fallback is present, then directly throw the exception. throw ex; } protected Object handleBlockException(InvocationContext ctx, SentinelResourceBinding annotation, BlockException ex) throws Throwable { // Execute block handler if configured. Method blockHandlerMethod = extractBlockHandlerMethod(ctx, annotation.blockHandler(), annotation.blockHandlerClass()); if (blockHandlerMethod != null) { Object[] originArgs = ctx.getParameters(); // Construct args. Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1); args[args.length - 1] = ex; try { if (isStatic(blockHandlerMethod)) { return blockHandlerMethod.invoke(null, args); } return blockHandlerMethod.invoke(ctx.getTarget(), args); } catch (InvocationTargetException e) { // throw the actual exception throw e.getTargetException(); } } // If no block handler is present, then go to fallback. return handleFallback(ctx, annotation, ex); } private Method extractFallbackMethod(InvocationContext ctx, String fallbackName, Class[] locationClass) { if (StringUtil.isBlank(fallbackName)) { return null; } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz = mustStatic ? locationClass[0] : ctx.getTarget().getClass(); Method originMethod = resolveMethod(ctx); MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName, originMethod.getParameterTypes()); if (m == null) { // First time, resolve the fallback. Method method = resolveFallbackInternal(originMethod, fallbackName, clazz, mustStatic); // Cache the method instance. ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, originMethod.getParameterTypes(), method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method extractDefaultFallbackMethod(InvocationContext ctx, String defaultFallback, Class[] locationClass) { if (StringUtil.isBlank(defaultFallback)) { SentinelResource annotationClass = ctx.getTarget().getClass().getAnnotation(SentinelResource.class); if (annotationClass != null && StringUtil.isNotBlank(annotationClass.defaultFallback())) { defaultFallback = annotationClass.defaultFallback(); if (locationClass == null || locationClass.length < 1) { locationClass = annotationClass.fallbackClass(); } } else { return null; } } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz = mustStatic ? locationClass[0] : ctx.getTarget().getClass(); MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback); if (m == null) { // First time, resolve the default fallback. Class originReturnType = resolveMethod(ctx).getReturnType(); // Default fallback allows two kinds of parameter list. // One is empty parameter list. Class[] defaultParamTypes = new Class[0]; // The other is a single parameter {@link Throwable} to get relevant exception info. Class[] paramTypeWithException = new Class[] {Throwable.class}; // We first find the default fallback with empty parameter list. Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes); // If default fallback with empty params is absent, we then try to find the other one. if (method == null) { method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException); } // Cache the method instance. ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method resolveFallbackInternal(Method originMethod, String name, Class clazz, boolean mustStatic) { // Fallback function allows two kinds of parameter list. Class[] defaultParamTypes = originMethod.getParameterTypes(); Class[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1); paramTypesWithException[paramTypesWithException.length - 1] = Throwable.class; // We first find the fallback matching the signature of origin method. Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes); // If fallback matching the origin method is absent, we then try to find the other one. if (method == null) { method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException); } return method; } private Method extractBlockHandlerMethod(InvocationContext ctx, String name, Class[] locationClass) { if (StringUtil.isBlank(name)) { return null; } boolean mustStatic = locationClass != null && locationClass.length >= 1; Class clazz; if (mustStatic) { clazz = locationClass[0]; } else { // By default current class. clazz = ctx.getTarget().getClass(); } Method originMethod = resolveMethod(ctx); MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name, originMethod.getParameterTypes()); if (m == null) { // First time, resolve the block handler. Method method = resolveBlockHandlerInternal(originMethod, name, clazz, mustStatic); // Cache the method instance. ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, originMethod.getParameterTypes(), method); return method; } if (!m.isPresent()) { return null; } return m.getMethod(); } private Method resolveBlockHandlerInternal(Method originMethod, String name, Class clazz, boolean mustStatic) { Class[] originList = originMethod.getParameterTypes(); Class[] parameterTypes = Arrays.copyOf(originList, originList.length + 1); parameterTypes[parameterTypes.length - 1] = BlockException.class; return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes); } private boolean checkStatic(boolean mustStatic, Method method) { return !mustStatic || isStatic(method); } private Method findMethod(boolean mustStatic, Class clazz, String name, Class returnType, Class... parameterTypes) { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (name.equals(method.getName()) && checkStatic(mustStatic, method) && returnType.isAssignableFrom(method.getReturnType()) && Arrays.equals(parameterTypes, method.getParameterTypes())) { RecordLog.info("Resolved method [{}] in class [{}]", name, clazz.getCanonicalName()); return method; } } // Current class not found, find in the super classes recursively. Class superClass = clazz.getSuperclass(); if (superClass != null && !Object.class.equals(superClass)) { return findMethod(mustStatic, superClass, name, returnType, parameterTypes); } else { String methodType = mustStatic ? " static" : ""; RecordLog.warn("Cannot find{} method [{}] in class [{}] with parameters {}", methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes)); return null; } } private boolean isStatic(Method method) { return Modifier.isStatic(method.getModifiers()); } protected Method resolveMethod(InvocationContext ctx) { Class targetClass = ctx.getTarget().getClass(); Method method = getDeclaredMethodFor(targetClass, ctx.getMethod().getName(), ctx.getMethod().getParameterTypes()); if (method == null) { throw new IllegalStateException("Cannot resolve target method: " + ctx.getMethod().getName()); } return method; } /** * Get declared method with provided name and parameterTypes in given class and its super classes. * All parameters should be valid. * * @param clazz class where the method is located * @param name method name * @param parameterTypes method parameter type list * @return resolved method, null if not found */ private Method getDeclaredMethodFor(Class clazz, String name, Class... parameterTypes) { try { return clazz.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException e) { Class superClass = clazz.getSuperclass(); if (superClass != null) { return getDeclaredMethodFor(superClass, name, parameterTypes); } } return null; } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapper.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import java.lang.reflect.Method; /** * @author Eric Zhao */ class MethodWrapper { private final Method method; private final boolean present; private MethodWrapper(Method method, boolean present) { this.method = method; this.present = present; } static MethodWrapper wrap(Method method) { if (method == null) { return none(); } return new MethodWrapper(method, true); } static MethodWrapper none() { return new MethodWrapper(null, false); } Method getMethod() { return method; } boolean isPresent() { return present; } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistry.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.util.StringUtil; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * Registry for resource configuration metadata (e.g. fallback method) * * @author Eric Zhao * @author dowenliu-xyz(hawkdowen@hotmail.com) */ final class ResourceMetadataRegistry { private static final Map FALLBACK_MAP = new ConcurrentHashMap<>(); private static final Map DEFAULT_FALLBACK_MAP = new ConcurrentHashMap<>(); private static final Map BLOCK_HANDLER_MAP = new ConcurrentHashMap<>(); /** * @deprecated use {@link #lookupFallback(Class, String, Class[])} */ @Deprecated static MethodWrapper lookupFallback(Class clazz, String name) { return FALLBACK_MAP.get(getKey(clazz, name)); } static MethodWrapper lookupFallback(Class clazz, String name, Class[] parameterTypes) { return FALLBACK_MAP.get(getKey(clazz, name, parameterTypes)); } static MethodWrapper lookupDefaultFallback(Class clazz, String name) { return DEFAULT_FALLBACK_MAP.get(getKey(clazz, name)); } /** * @deprecated use {@link #lookupBlockHandler(Class, String, Class[])} */ @Deprecated static MethodWrapper lookupBlockHandler(Class clazz, String name) { return BLOCK_HANDLER_MAP.get(getKey(clazz, name)); } static MethodWrapper lookupBlockHandler(Class clazz, String name, Class[] parameterTypes) { return BLOCK_HANDLER_MAP.get(getKey(clazz, name, parameterTypes)); } /** * @deprecated use {@link #updateFallbackFor(Class, String, Class[], Method)} */ @Deprecated static void updateFallbackFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } static void updateFallbackFor(Class clazz, String handlerName, Class[] parameterTypes, Method handlerMethod) { if (clazz == null || StringUtil.isBlank(handlerName)) { throw new IllegalArgumentException("Bad argument"); } FALLBACK_MAP.put(getKey(clazz, handlerName, parameterTypes), MethodWrapper.wrap(handlerMethod)); } static void updateDefaultFallbackFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } DEFAULT_FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } /** * @deprecated use {@link #updateBlockHandlerFor(Class, String, Class[], Method)} */ @Deprecated static void updateBlockHandlerFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } BLOCK_HANDLER_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method)); } static void updateBlockHandlerFor(Class clazz, String handlerName, Class[] parameterTypes, Method handlerMethod) { if (clazz == null || StringUtil.isBlank(handlerName)) { throw new IllegalArgumentException("Bad argument"); } BLOCK_HANDLER_MAP.put(getKey(clazz, handlerName, parameterTypes), MethodWrapper.wrap(handlerMethod)); } private static String getKey(Class clazz, String name) { return String.format("%s:%s", clazz.getCanonicalName(), name); } private static String getKey(Class clazz, String name, Class[] parameterTypes) { return String.format( "%s:%s;%s", clazz.getCanonicalName(), name, Arrays.stream(parameterTypes).map(Class::getCanonicalName).collect(Collectors.joining(",")) ); } /** * Only for internal test. */ static void clearFallbackMap() { FALLBACK_MAP.clear(); } /** * Only for internal test. */ static void clearBlockHandlerMap() { BLOCK_HANDLER_MAP.clear(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceBinding.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.EntryType; import javax.enterprise.util.Nonbinding; import javax.interceptor.InterceptorBinding; import java.lang.annotation.*; /** * The annotation indicates a definition of Sentinel resource. * * @author Eric Zhao * @author seasidesky * @since 1.8.0 */ @InterceptorBinding @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface SentinelResourceBinding { /** * @return name of the Sentinel resource */ @Nonbinding String value() default ""; /** * @return the entry type (inbound or outbound), outbound by default */ @Nonbinding EntryType entryType() default EntryType.OUT; /** * @return the classification (type) of the resource */ @Nonbinding int resourceType() default 0; /** * @return name of the block exception function, empty by default */ @Nonbinding String blockHandler() default ""; /** * The {@code blockHandler} is located in the same class with the original method by default. * However, if some methods share the same signature and intend to set the same block handler, * then users can set the class where the block handler exists. Note that the block handler method * must be static. * * @return the class where the block handler exists, should not provide more than one classes */ @Nonbinding Class[] blockHandlerClass() default {}; /** * @return name of the fallback function, empty by default */ @Nonbinding String fallback() default ""; /** * The {@code defaultFallback} is used as the default universal fallback method. * It should not accept any parameters, and the return type should be compatible * with the original method. * * @return name of the default fallback method, empty by default */ @Nonbinding String defaultFallback() default ""; /** * The {@code fallback} is located in the same class with the original method by default. * However, if some methods share the same signature and intend to set the same fallback, * then users can set the class where the fallback function exists. Note that the shared fallback method * must be static. * * @return the class where the fallback method is located (only single class) */ @Nonbinding Class[] fallbackClass() default {}; /** * @return the list of exception classes to trace, {@link Throwable} by default */ @Nonbinding Class[] exceptionsToTrace() default {Throwable.class}; /** * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore} * will be of higher precedence. * * @return the list of exception classes to ignore, empty by default */ @Nonbinding Class[] exceptionsToIgnore() default {}; } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceInterceptor.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import javax.annotation.Priority; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; /** * @author sea * @since 1.8.0 */ @Interceptor @SentinelResourceBinding @Priority(0) public class SentinelResourceInterceptor extends AbstractSentinelInterceptorSupport { @AroundInvoke Object aroundInvoke(InvocationContext ctx) throws Throwable { SentinelResourceBinding annotation = ctx.getMethod().getAnnotation(SentinelResourceBinding.class); if (annotation == null) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } String resourceName = getResourceName(annotation.value(), ctx.getMethod()); EntryType entryType = annotation.entryType(); int resourceType = annotation.resourceType(); Entry entry = null; try { entry = SphU.entry(resourceName, resourceType, entryType, ctx.getParameters()); Object result = ctx.proceed(); return result; } catch (BlockException ex) { return handleBlockException(ctx, annotation, ex); } catch (Throwable ex) { Class[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(ctx, annotation, ex); } // No fallback function can handle the exception, so throw it out. throw ex; } finally { if (entry != null) { entry.exit(1, ctx.getParameters()); } } } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml ================================================ ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupportTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; /** * @author sea */ public class AbstractSentinelInterceptorSupportTest extends AbstractSentinelInterceptorSupport { @Test public void testGetResourceName() throws Exception { Method method = FooService.class.getMethod("random"); String resourceName = "someRandom"; String expectedResolvedName = FooService.class.getName() + ":random()"; assertThat(getResourceName(resourceName, method)).isEqualTo(resourceName); assertThat(getResourceName(null, method)).isEqualTo(expectedResolvedName); assertThat(getResourceName("", method)).isEqualTo(expectedResolvedName); } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapperTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class MethodWrapperTest { @Test public void testWrapMethod() { Method method = String.class.getMethods()[0]; MethodWrapper m = MethodWrapper.wrap(method); assertThat(m.isPresent()).isTrue(); assertThat(m.getMethod()).isSameAs(method); } @Test public void testNone() { MethodWrapper none = MethodWrapper.none(); assertThat(none.isPresent()).isFalse(); assertThat(none.getMethod()).isNull(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistryTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao * @author dowenliu-xyz(hawkdowen@hotmail.com) */ public class ResourceMetadataRegistryTest { @Before public void setUp() throws Exception { ResourceMetadataRegistry.clearBlockHandlerMap(); ResourceMetadataRegistry.clearFallbackMap(); } @After public void tearDown() throws Exception { ResourceMetadataRegistry.clearBlockHandlerMap(); ResourceMetadataRegistry.clearFallbackMap(); } @Test public void testUpdateThenLookupFallback() { Class clazz = FooService.class; String methodName = "someMethodFallback"; Class[] parameterTypes = new Class[] {String.class}; Method method = clazz.getMethods()[0]; assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes)).isNull(); ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, parameterTypes, null); assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes).isPresent()).isFalse(); ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, parameterTypes, method); MethodWrapper wrapper = ResourceMetadataRegistry.lookupFallback(clazz, methodName, parameterTypes); assertThat(wrapper.isPresent()).isTrue(); assertThat(wrapper.getMethod()).isSameAs(method); } @Test public void testUpdateThenLookupBlockHandler() { Class clazz = FooService.class; String methodName = "someMethodBlockHand;er"; Class[] parameterTypes = new Class[] {String.class}; Method method = clazz.getMethods()[1]; assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes)).isNull(); ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, parameterTypes, null); assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes).isPresent()).isFalse(); ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, parameterTypes, method); MethodWrapper wrapper = ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName, parameterTypes); assertThat(wrapper.isPresent()).isTrue(); assertThat(wrapper.getMethod()).isSameAs(method); } @Test(expected = IllegalArgumentException.class) public void testUpdateBlockHandlerBadArgument() { ResourceMetadataRegistry.updateBlockHandlerFor(null, "sxs", new Class[0], String.class.getMethods()[0]); } @Test(expected = IllegalArgumentException.class) public void testUpdateFallbackBadArgument() { ResourceMetadataRegistry.updateBlockHandlerFor(String.class, "", new Class[0], String.class.getMethods()[0]); } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/SentinelAnnotationInterceptorIntegrationTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.MethodUtil; import org.junit.*; import javax.enterprise.inject.se.SeContainer; import javax.enterprise.inject.se.SeContainerInitializer; import java.util.ArrayList; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; /** * @author sea */ @Ignore public class SentinelAnnotationInterceptorIntegrationTest { static SeContainer container; FooService fooService; @BeforeClass public static void init() { SeContainerInitializer containerInit = SeContainerInitializer.newInstance(); container = containerInit.initialize(); } @AfterClass public static void shutdown() { container.close(); } @Before public void setUp() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); fooService = container.select(FooService.class).get(); } @After public void tearDown() throws Exception { FlowRuleManager.loadRules(new ArrayList()); ClusterBuilderSlot.resetClusterNodes(); } @Test public void testForeignBlockHandlerClass() throws Exception { assertThat(fooService.random()).isNotEqualTo(FooUtil.BLOCK_FLAG); String resourceName = MethodUtil.resolveMethodName(FooService.class.getDeclaredMethod("random")); ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.random()).isEqualTo(FooUtil.BLOCK_FLAG); assertThat(cn.blockQps()).isPositive(); } @Test(expected = FlowException.class) public void testBlockHandlerNotFound() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); fooService.baz("Sentinel"); } @Test public void testAnnotationExceptionsToIgnore() { assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); String resourceName = "apiBaz"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); try { fooService.baz("fail"); fail("should not reach here"); } catch (IllegalMonitorStateException ex) { assertThat(cn.exceptionQps()).isZero(); } } @Test public void testFallbackWithNoParams() throws Exception { assertThat(fooService.fooWithFallback(1)).isEqualTo("Hello for 1"); String resourceName = "apiFooWithFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Fallback should be ignored for this. try { fooService.fooWithFallback(5758); fail("should not reach here"); } catch (IllegalAccessException e) { assertThat(cn.exceptionQps()).isZero(); } // Fallback should take effect. assertThat(fooService.fooWithFallback(5763)).isEqualTo("eee..."); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Fallback should not take effect for BlockException, as blockHandler is configured. assertThat(fooService.fooWithFallback(2221)).isEqualTo("Oops, 2221"); assertThat(cn.blockQps()).isPositive(); } @Test public void testDefaultFallbackWithSingleParam() { assertThat(fooService.anotherFoo(1)).isEqualTo("Hello for 1"); String resourceName = "apiAnotherFooWithDefaultFallback"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Default fallback should take effect. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.exceptionQps()).isPositive(); assertThat(cn.blockQps()).isZero(); FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); // Default fallback should also take effect for BlockException. assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); assertThat(cn.blockQps()).isPositive(); } @Test public void testNormalBlockHandlerAndFallback() throws Exception { assertThat(fooService.foo(1)).isEqualTo("Hello for 1"); String resourceName = "apiFoo"; ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); assertThat(cn).isNotNull(); assertThat(cn.passQps()).isPositive(); // Test for biz exception. try { fooService.foo(5758); fail("should not reach here"); } catch (Exception ex) { // Should not be traced. assertThat(cn.exceptionQps()).isZero(); } try { fooService.foo(5763); fail("should not reach here"); } catch (Exception ex) { assertThat(cn.exceptionQps()).isPositive(); } // Test for blockHandler FlowRuleManager.loadRules(Collections.singletonList( new FlowRule(resourceName).setCount(0) )); assertThat(fooService.foo(1121)).isEqualTo("Oops, 1121"); assertThat(cn.blockQps()).isPositive(); } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooService.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service; import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding; import com.alibaba.csp.sentinel.slots.block.BlockException; import javax.enterprise.context.ApplicationScoped; import java.util.concurrent.ThreadLocalRandom; /** * @author Eric Zhao * @author sea */ @ApplicationScoped public class FooService { @SentinelResourceBinding(value = "apiFoo", blockHandler = "fooBlockHandler", exceptionsToTrace = {IllegalArgumentException.class}) public String foo(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResourceBinding(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc", exceptionsToTrace = {IllegalArgumentException.class}) public String fooWithFallback(int i) throws Exception { if (i == 5758) { throw new IllegalAccessException(); } if (i == 5763) { throw new IllegalArgumentException(); } return "Hello for " + i; } @SentinelResourceBinding(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback", fallbackClass = {FooUtil.class}) public String anotherFoo(int i) { if (i == 5758) { throw new IllegalArgumentException("oops"); } return "Hello for " + i; } @SentinelResourceBinding(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class) public int random() { return ThreadLocalRandom.current().nextInt(0, 30000); } @SentinelResourceBinding(value = "apiBaz", blockHandler = "bazBlockHandler", exceptionsToIgnore = {IllegalMonitorStateException.class}) public String baz(String name) { if (name.equals("fail")) { throw new IllegalMonitorStateException("boom!"); } return "cheers, " + name; } public String fooBlockHandler(int i, BlockException ex) { return "Oops, " + i; } public String fooFallbackFunc(int i) { return "eee..."; } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooUtil.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author Eric Zhao */ public class FooUtil { public static final int BLOCK_FLAG = 88888; public static final String FALLBACK_DEFAULT_RESULT = "fallback"; public static int globalBlockHandler(BlockException ex) { System.out.println("Oops: " + ex.getClass().getSimpleName()); return BLOCK_FLAG; } public static String globalDefaultFallback(Throwable t) { System.out.println("Fallback caught: " + t.getClass().getSimpleName()); return FALLBACK_DEFAULT_RESULT; } } ================================================ FILE: sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/resources/META-INF/beans.xml ================================================ com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor ================================================ FILE: sentinel-extension/sentinel-datasource-apollo/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-apollo 1.5.0 com.alibaba.csp sentinel-datasource-extension com.ctrip.framework.apollo apollo-client ${apollo.version} ================================================ FILE: sentinel-extension/sentinel-datasource-apollo/src/main/java/com/alibaba/csp/sentinel/datasource/apollo/ApolloDataSource.java ================================================ package com.alibaba.csp.sentinel.datasource.apollo; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Sets; /** * A read-only {@code DataSource} with Apollo as its configuration * source. *
          * When the rule is changed in Apollo, it will take effect in real time. * * @author Jason Song * @author Haojun Ren */ public class ApolloDataSource extends AbstractDataSource { private final Config config; private final String ruleKey; private final String defaultRuleValue; private ConfigChangeListener configChangeListener; /** * Constructs the Apollo data source * * @param namespaceName the namespace name in Apollo, should not be null or empty * @param ruleKey the rule key in the namespace, should not be null or empty * @param defaultRuleValue the default rule value when the ruleKey is not found or any error * occurred * @param parser the parser to transform string configuration to actual flow rules */ public ApolloDataSource(String namespaceName, String ruleKey, String defaultRuleValue, Converter parser) { super(parser); Preconditions.checkArgument(!Strings.isNullOrEmpty(namespaceName), "Namespace name could not be null or empty"); Preconditions.checkArgument(!Strings.isNullOrEmpty(ruleKey), "RuleKey could not be null or empty!"); this.ruleKey = ruleKey; this.defaultRuleValue = defaultRuleValue; this.config = ConfigService.getConfig(namespaceName); initialize(); RecordLog.info("Initialized rule for namespace: {}, rule key: {}", namespaceName, ruleKey); } private void initialize() { initializeConfigChangeListener(); loadAndUpdateRules(); } private void loadAndUpdateRules() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn("[ApolloDataSource] WARN: rule config is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Throwable ex) { RecordLog.warn("[ApolloDataSource] Error when loading rule config", ex); } } private void initializeConfigChangeListener() { configChangeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { ConfigChange change = changeEvent.getChange(ruleKey); //change is never null because the listener will only notify for this key if (change != null) { RecordLog.info("[ApolloDataSource] Received config changes: {}", change); } loadAndUpdateRules(); } }; config.addChangeListener(configChangeListener, Sets.newHashSet(ruleKey)); } @Override public String readSource() throws Exception { return config.getProperty(ruleKey, defaultRuleValue); } @Override public void close() throws Exception { config.removeChangeListener(configChangeListener); } } ================================================ FILE: sentinel-extension/sentinel-datasource-consul/README.md ================================================ # Sentinel DataSource Consul Sentinel DataSource Consul provides integration with Consul. The data source leverages blocking query (backed by long polling) of Consul. ## Usage To use Sentinel DataSource Consul, you could add the following dependency: ```xml com.alibaba.csp sentinel-datasource-consul x.y.z ``` Then you can create a `ConsulDataSource` and register to rule managers. For instance: ```java ReadableDataSource> dataSource = new ConsulDataSource<>(host, port, ruleKey, waitTimeoutInSecond, flowConfigParser); FlowRuleManager.register2Property(dataSource.getProperty()); ``` - `ruleKey`: the rule persistence key - `waitTimeoutInSecond`: long polling timeout (in second) of the Consul API client ================================================ FILE: sentinel-extension/sentinel-datasource-consul/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-consul jar 1.8 1.8 1.4.5 org.codehaus.groovy groovy-xml 3.0.6 test com.alibaba.csp sentinel-datasource-extension com.ecwid.consul consul-api ${consul.version} org.testcontainers consul 1.19.7 test junit junit test com.alibaba fastjson test ================================================ FILE: sentinel-extension/sentinel-datasource-consul/src/main/java/com/alibaba/csp/sentinel/datasource/consul/ConsulDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.consul; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.QueryParams; import com.ecwid.consul.v1.Response; import com.ecwid.consul.v1.kv.model.GetValue; import java.util.concurrent.*; /** *

          * A read-only {@code DataSource} with Consul backend. *

          *

          * The data source first initial rules from a Consul during initialization. * Then it start a watcher to observe the updates of rule date and update to memory. * * Consul do not provide http api to watch the update of KV,so it use a long polling and * blocking queries of the Consul's feature * to watch and update value easily.When Querying data by index will blocking until change or timeout. If * the index of the current query is larger than before, it means that the data has changed. *

          * * @author wavesZh * @author Zhiguo.Chen */ public class ConsulDataSource extends AbstractDataSource { private static final int DEFAULT_PORT = 8500; private final String address; private final String token; private final String ruleKey; /** * Request of query will hang until timeout (in second) or get updated value. */ private final int watchTimeout; /** * Record the data's index in Consul to watch the change. * If lastIndex is smaller than the index of next query, it means that rule data has updated. */ private volatile long lastIndex; private final ConsulClient client; private final ConsulKVWatcher watcher = new ConsulKVWatcher(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private final ExecutorService watcherService = Executors.newSingleThreadExecutor( new NamedThreadFactory("sentinel-consul-ds-watcher", true)); public ConsulDataSource(String host, String ruleKey, int watchTimeoutInSecond, Converter parser) { this(host, DEFAULT_PORT, ruleKey, watchTimeoutInSecond, parser); } /** * Constructor of {@code ConsulDataSource}. * * @param parser customized data parser, cannot be empty * @param host consul agent host * @param port consul agent port * @param ruleKey data key in Consul * @param watchTimeout request for querying data will be blocked until new data or timeout. The unit is second (s) */ public ConsulDataSource(String host, int port, String ruleKey, int watchTimeout, Converter parser) { this(host, port, null, ruleKey, watchTimeout, parser); } /** * Constructor of {@code ConsulDataSource}. * * @param parser customized data parser, cannot be empty * @param host consul agent host * @param port consul agent port * @param token consul agent acl token * @param ruleKey data key in Consul * @param watchTimeout request for querying data will be blocked until new data or timeout. The unit is second (s) */ public ConsulDataSource(String host, int port, String token, String ruleKey, int watchTimeout, Converter parser) { super(parser); AssertUtil.notNull(host, "Consul host can not be null"); AssertUtil.notEmpty(ruleKey, "Consul ruleKey can not be empty"); AssertUtil.isTrue(watchTimeout >= 0, "watchTimeout should not be negative"); this.client = new ConsulClient(host, port); this.address = host + ":" + port; this.token = token; this.ruleKey = ruleKey; this.watchTimeout = watchTimeout; loadInitialConfig(); startKVWatcher(); } private void startKVWatcher() { watcherService.submit(watcher); } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn( "[ConsulDataSource] WARN: initial config is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[ConsulDataSource] Error when loading initial config", ex); } } @Override public String readSource() throws Exception { if (this.client == null) { throw new IllegalStateException("Consul has not been initialized or error occurred"); } Response response = getValueImmediately(ruleKey); if (response != null) { GetValue value = response.getValue(); lastIndex = response.getConsulIndex(); return value != null ? value.getDecodedValue() : null; } return null; } @Override public void close() throws Exception { watcher.stop(); watcherService.shutdown(); } private class ConsulKVWatcher implements Runnable { private volatile boolean running = true; @Override public void run() { while (running) { // It will be blocked until watchTimeout(s) if rule data has no update. Response response = getValue(ruleKey, lastIndex, watchTimeout); if (response == null) { try { TimeUnit.MILLISECONDS.sleep(watchTimeout * 1000); } catch (InterruptedException e) { } continue; } GetValue getValue = response.getValue(); Long currentIndex = response.getConsulIndex(); if (currentIndex == null || currentIndex <= lastIndex) { continue; } lastIndex = currentIndex; if (getValue != null) { String newValue = getValue.getDecodedValue(); try { getProperty().updateValue(parser.convert(newValue)); RecordLog.info("[ConsulDataSource] New property value received for ({}, {}): {}", address, ruleKey, newValue); } catch (Exception ex) { // In case of parsing error. RecordLog.warn("[ConsulDataSource] Failed to update value for ({}, {}), raw value: {}", address, ruleKey, newValue); } } } } private void stop() { running = false; } } /** * Get data from Consul immediately. * * @param key data key in Consul * @return the value associated to the key, or null if error occurs */ private Response getValueImmediately(String key) { return getValue(key, -1, -1); } /** * Get data from Consul (blocking). * * @param key data key in Consul * @param index the index of data in Consul. * @param waitTime time(second) for waiting get updated value. * @return the value associated to the key, or null if error occurs */ private Response getValue(String key, long index, long waitTime) { try { if (StringUtil.isNotBlank(token)) { return client.getKVValue(key, token, new QueryParams(waitTime, index)); } else { return client.getKVValue(key, new QueryParams(waitTime, index)); } } catch (Throwable t) { RecordLog.warn("[ConsulDataSource] Failed to get value for key: " + key, t); } return null; } } ================================================ FILE: sentinel-extension/sentinel-datasource-consul/src/test/java/com/alibaba/csp/sentinel/datasource/consul/ConsulDataSourceTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.consul; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.Response; import org.junit.*; import org.testcontainers.consul.ConsulContainer; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; /** * @author wavesZh */ public class ConsulDataSourceTest { @ClassRule public static ConsulContainer consulContainer = new ConsulContainer("hashicorp/consul:1.15"); private final String ruleKey = "sentinel.rules.flow.ruleKey"; private final int waitTimeoutInSecond = 1; private ConsulClient client; private ReadableDataSource> consulDataSource; private List rules; @Before public void init() { int port = consulContainer.getMappedPort(8500); String host = consulContainer.getHost(); client = new ConsulClient(host, port); Converter> flowConfigParser = buildFlowConfigParser(); String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; initConsulRuleData(flowRulesJson); rules = flowConfigParser.convert(flowRulesJson); consulDataSource = new ConsulDataSource<>(host, port, ruleKey, waitTimeoutInSecond, flowConfigParser); FlowRuleManager.register2Property(consulDataSource.getProperty()); } @After public void clean() throws Exception { if (consulDataSource != null) { consulDataSource.close(); } FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testConsulDataSourceWhenInit() { List rules = FlowRuleManager.getRules(); Assert.assertEquals(this.rules, rules); } @Test public void testConsulDataSourceWhenUpdate() throws InterruptedException { rules.get(0).setMaxQueueingTimeMs(new Random().nextInt()); client.setKVValue(ruleKey, JSON.toJSONString(rules)); TimeUnit.SECONDS.sleep(waitTimeoutInSecond); List rules = FlowRuleManager.getRules(); Assert.assertEquals(this.rules, rules); } private Converter> buildFlowConfigParser() { return source -> JSON.parseObject(source, new TypeReference>() {}); } private void initConsulRuleData(String flowRulesJson) { Response response = client.setKVValue(ruleKey, flowRulesJson); Assert.assertEquals(Boolean.TRUE, response.getValue()); } } ================================================ FILE: sentinel-extension/sentinel-datasource-etcd/README.md ================================================ # Sentinel DataSource Etcd Sentinel DataSource Etcd provides integration with etcd so that etcd can be the dynamic rule data source of Sentinel. The data source uses push model (watcher). To use Sentinel DataSource Etcd, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-etcd x.y.z ``` We could configure Etcd connection configuration by config file (for example `sentinel.properties`): ```properties csp.sentinel.etcd.endpoints=http://ip1:port1,http://ip2:port2 csp.sentinel.etcd.user=your_user csp.sentinel.etcd.password=your_password csp.sentinel.etcd.charset=your_charset csp.sentinel.etcd.auth.enable=true # if ture, then open user/password or ssl check csp.sentinel.etcd.authority=authority # ssl ``` Or we could configure via JVM -D args or via `SentinelConfig.setConfig(key, value)`. Then we can create an `EtcdDataSource` and register to rule managers. For instance: ```java // `rule_key` is the rule config key ReadableDataSource> flowRuleEtcdDataSource = new EtcdDataSource<>(rule_key, (rule) -> JSON.parseArray(rule, FlowRule.class)); FlowRuleManager.register2Property(flowRuleEtcdDataSource.getProperty()); ``` We've also provided an example: [sentinel-demo-etcd-datasource](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-etcd-datasource) ================================================ FILE: sentinel-extension/sentinel-datasource-etcd/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-etcd jar 1.8 1.8 0.3.0 com.alibaba.csp sentinel-datasource-extension io.etcd jetcd-core ${jetcd.version} junit junit test com.alibaba fastjson test org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.source} ${maven.compiler.target} ================================================ FILE: sentinel-extension/sentinel-datasource-etcd/src/main/java/com/alibaba/csp/sentinel/datasource/etcd/EtcdConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.etcd; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.StringUtil; /** * Etcd connection configuration. * * @author lianglin * @since 1.7.0 */ public final class EtcdConfig { public final static String END_POINTS = "csp.sentinel.etcd.endpoints"; public final static String USER = "csp.sentinel.etcd.user"; public final static String PASSWORD = "csp.sentinel.etcd.password"; public final static String CHARSET = "csp.sentinel.etcd.charset"; public final static String AUTH_ENABLE = "csp.sentinel.etcd.auth.enable"; public final static String AUTHORITY = "csp.sentinel.etcd.authority"; private final static String ENABLED = "true"; public static String getEndPoints() { return SentinelConfig.getConfig(END_POINTS); } public static String getUser() { return SentinelConfig.getConfig(USER); } public static String getPassword() { return SentinelConfig.getConfig(PASSWORD); } public static String getCharset() { String etcdCharset = SentinelConfig.getConfig(CHARSET); if (StringUtil.isNotBlank(etcdCharset)) { return etcdCharset; } return SentinelConfig.charset(); } public static boolean isAuthEnable() { return ENABLED.equalsIgnoreCase(SentinelConfig.getConfig(AUTH_ENABLE)); } public static String getAuthority() { return SentinelConfig.getConfig(AUTHORITY); } private EtcdConfig() {} } ================================================ FILE: sentinel-extension/sentinel-datasource-etcd/src/main/java/com/alibaba/csp/sentinel/datasource/etcd/EtcdDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.etcd; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import io.etcd.jetcd.ByteSequence; import io.etcd.jetcd.Client; import io.etcd.jetcd.KeyValue; import io.etcd.jetcd.Watch; import io.etcd.jetcd.kv.GetResponse; import io.etcd.jetcd.watch.WatchEvent; import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.CompletableFuture; /** * A read-only {@code DataSource} with Etcd backend. When the data in Etcd backend has been modified, * Etcd will automatically push the new value so that the dynamic configuration can be real-time. * * @author lianglin * @since 1.7.0 */ public class EtcdDataSource extends AbstractDataSource { private final Client client; private Watch.Watcher watcher; private final String key; private Charset charset = Charset.forName(EtcdConfig.getCharset()); /** * Create an etcd data-source. The connection configuration will be retrieved from {@link EtcdConfig}. * * @param key config key * @param parser data parser */ public EtcdDataSource(String key, Converter parser) { super(parser); if (!EtcdConfig.isAuthEnable()) { this.client = Client.builder() .endpoints(EtcdConfig.getEndPoints().split(",")).build(); } else { this.client = Client.builder() .endpoints(EtcdConfig.getEndPoints().split(",")) .user(ByteSequence.from(EtcdConfig.getUser(), charset)) .password(ByteSequence.from(EtcdConfig.getPassword(), charset)) .authority(EtcdConfig.getAuthority()) .build(); } this.key = key; loadInitialConfig(); initWatcher(); } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn( "[EtcdDataSource] Initial configuration is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[EtcdDataSource] Error when loading initial configuration", ex); } } private void initWatcher() { watcher = client.getWatchClient().watch(ByteSequence.from(key, charset), (watchResponse) -> { for (WatchEvent watchEvent : watchResponse.getEvents()) { WatchEvent.EventType eventType = watchEvent.getEventType(); if (eventType == WatchEvent.EventType.PUT) { try { String newValueJson = watchEvent.getKeyValue().getValue().toString(charset); T newValue = parser.convert(newValueJson); getProperty().updateValue(newValue); } catch (Exception e) { RecordLog.warn("[EtcdDataSource] Failed to update config", e); } } else if (eventType == WatchEvent.EventType.DELETE) { RecordLog.info("[EtcdDataSource] Cleaning config for key <{}>", key); getProperty().updateValue(null); } } }); } @Override public String readSource() throws Exception { CompletableFuture responseFuture = client.getKVClient().get(ByteSequence.from(key, charset)); List kvs = responseFuture.get().getKvs(); return kvs.size() == 0 ? null : kvs.get(0).getValue().toString(charset); } @Override public void close() { if (watcher != null) { try { watcher.close(); } catch (Exception ex) { RecordLog.info("[EtcdDataSource] Failed to close watcher", ex); } } if (client != null) { client.close(); } } } ================================================ FILE: sentinel-extension/sentinel-datasource-etcd/src/test/java/com/alibaba/csp/sentinel/datasource/etcd/EtcdDataSourceTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.etcd; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import io.etcd.jetcd.ByteSequence; import io.etcd.jetcd.Client; import io.etcd.jetcd.KV; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * @author lianglin * @since 1.7.0 */ @Ignore(value = "Before run this test, you need to set up your etcd server.") public class EtcdDataSourceTest { private final String endPoints = "http://127.0.0.1:2379"; @Before public void setUp() { SentinelConfig.setConfig(EtcdConfig.END_POINTS, endPoints); FlowRuleManager.loadRules(new ArrayList<>()); } @After public void tearDown() { SentinelConfig.setConfig(EtcdConfig.END_POINTS, ""); FlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testReadSource() throws Exception { EtcdDataSource dataSource = new EtcdDataSource("foo", value -> value); KV kvClient = Client.builder() .endpoints(endPoints) .build().getKVClient(); kvClient.put(ByteSequence.from("foo".getBytes()), ByteSequence.from("test".getBytes())); Assert.assertNotNull(dataSource.readSource().equals("test")); kvClient.put(ByteSequence.from("foo".getBytes()), ByteSequence.from("test2".getBytes())); Assert.assertNotNull(dataSource.getProperty().equals("test2")); } @Test public void testDynamicUpdate() throws InterruptedException { String demo_key = "etcd_demo_key"; ReadableDataSource> flowRuleEtcdDataSource = new EtcdDataSource<>(demo_key, (value) -> JSON.parseArray(value, FlowRule.class)); FlowRuleManager.register2Property(flowRuleEtcdDataSource.getProperty()); KV kvClient = Client.builder() .endpoints(endPoints) .build().getKVClient(); final String rule1 = "[\n" + " {\n" + " \"resource\": \"TestResource\",\n" + " \"controlBehavior\": 0,\n" + " \"count\": 5.0,\n" + " \"grade\": 1,\n" + " \"limitApp\": \"default\",\n" + " \"strategy\": 0\n" + " }\n" + "]"; kvClient.put(ByteSequence.from(demo_key.getBytes()), ByteSequence.from(rule1.getBytes())); Thread.sleep(1000); FlowRule flowRule = FlowRuleManager.getRules().get(0); Assert.assertTrue(flowRule.getResource().equals("TestResource")); Assert.assertTrue(flowRule.getCount() == 5.0); Assert.assertTrue(flowRule.getGrade() == 1); final String rule2 = "[\n" + " {\n" + " \"resource\": \"TestResource\",\n" + " \"controlBehavior\": 0,\n" + " \"count\": 6.0,\n" + " \"grade\": 3,\n" + " \"limitApp\": \"default\",\n" + " \"strategy\": 0\n" + " }\n" + "]"; kvClient.put(ByteSequence.from(demo_key.getBytes()), ByteSequence.from(rule2.getBytes())); Thread.sleep(1000); flowRule = FlowRuleManager.getRules().get(0); Assert.assertTrue(flowRule.getResource().equals("TestResource")); Assert.assertTrue(flowRule.getCount() == 6.0); Assert.assertTrue(flowRule.getGrade() == 3); } } ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/README.md ================================================ # Sentinel DataSource Eureka Sentinel DataSource Eureka provides integration with [Eureka](https://github.com/Netflix/eureka) so that Eureka can be the dynamic rule data source of Sentinel. To use Sentinel DataSource Eureka, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-eureka x.y.z ``` Then you can create an `EurekaDataSource` and register to rule managers. SDK usage: ```java EurekaDataSource> eurekaDataSource = new EurekaDataSource("app-id", "instance-id", Arrays.asList("http://localhost:8761/eureka", "http://localhost:8762/eureka", "http://localhost:8763/eureka"), "rule-key", flowRuleParser); FlowRuleManager.register2Property(eurekaDataSource.getProperty()); ``` Example for Spring Cloud Application: ```java @Bean public EurekaDataSource> eurekaDataSource(EurekaInstanceConfig eurekaInstanceConfig, EurekaClientConfig eurekaClientConfig) { List serviceUrls = EndpointUtils.getServiceUrlsFromConfig(eurekaClientConfig, eurekaInstanceConfig.getMetadataMap().get("zone"), eurekaClientConfig.shouldPreferSameZoneEureka()); EurekaDataSource> eurekaDataSource = new EurekaDataSource(eurekaInstanceConfig.getAppname(), eurekaInstanceConfig.getInstanceId(), serviceUrls, "flowrules", new Converter>() { @Override public List convert(String o) { return JSON.parseObject(o, new TypeReference>() { }); } }); FlowRuleManager.register2Property(eurekaDataSource.getProperty()); return eurekaDataSource; } ``` To refresh the rule dynamically, you need to call [Eureka-REST-operations](https://github.com/Netflix/eureka/wiki/Eureka-REST-operations) to update instance metadata: ```plaintext PUT /eureka/apps/{appID}/{instanceID}/metadata?{ruleKey}={json of the rules} ``` Note: don't forget to encode your JSON string in the url. ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-eureka 2.6.1 3.1.0 com.alibaba.csp sentinel-datasource-extension com.alibaba fastjson com.fasterxml.jackson.core jackson-databind 2.13.4.2 junit junit test org.awaitility awaitility test org.springframework.boot spring-boot-starter-test ${spring.boot.version} test org.springframework.cloud spring-cloud-starter-netflix-eureka-server ${spring.cloud.version} test com.google.code.gson gson ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/src/main/java/com/alibaba/csp/sentinel/datasource/eureka/EurekaDataSource.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.eureka; import com.alibaba.csp.sentinel.datasource.AutoRefreshDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import java.io.*; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** *

          * A {@link ReadableDataSource} based on Eureka. This class will automatically * fetches the metadata of the instance every period. *

          *

          * Limitations: Default refresh interval is 10s. Because there is synchronization between eureka servers, * it may take longer to take effect. *

          * * @author liyang * @since 1.8.0 */ public class EurekaDataSource extends AutoRefreshDataSource { private static final long DEFAULT_REFRESH_MS = 10000; /** * Default connect timeout: 3s */ private static final int DEFAULT_CONNECT_TIMEOUT_MS = 3000; /** * Default read timeout: 30s */ private static final int DEFAULT_READ_TIMEOUT_MS = 30000; private final int connectTimeoutMills; private final int readTimeoutMills; /** * Eureka instance app ID. */ private final String appId; /** * Eureka instance id. */ private final String instanceId; /** * Eureka server URL list. */ private final List serviceUrls; /** * Metadata key of the rule source. */ private final String ruleKey; public EurekaDataSource(String appId, String instanceId, List serviceUrls, String ruleKey, Converter configParser) { this(appId, instanceId, serviceUrls, ruleKey, configParser, DEFAULT_REFRESH_MS, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } public EurekaDataSource(String appId, String instanceId, List serviceUrls, String ruleKey, Converter configParser, long refreshMs, int connectTimeoutMills, int readTimeoutMills) { super(configParser, refreshMs); AssertUtil.notNull(appId, "appId can't be null"); AssertUtil.notNull(instanceId, "instanceId can't be null"); AssertUtil.assertNotEmpty(serviceUrls, "serviceUrls can't be empty"); AssertUtil.notNull(ruleKey, "ruleKey can't be null"); AssertUtil.assertState(connectTimeoutMills > 0, "connectTimeoutMills must be greater than 0"); AssertUtil.assertState(readTimeoutMills > 0, "readTimeoutMills must be greater than 0"); this.appId = appId; this.instanceId = instanceId; this.serviceUrls = ensureEndWithSlash(serviceUrls); AssertUtil.assertNotEmpty(this.serviceUrls, "No available service url"); this.ruleKey = ruleKey; this.connectTimeoutMills = connectTimeoutMills; this.readTimeoutMills = readTimeoutMills; } private List ensureEndWithSlash(List serviceUrls) { List newServiceUrls = new ArrayList<>(); for (String serviceUrl : serviceUrls) { if (StringUtil.isBlank(serviceUrl)) { continue; } if (!serviceUrl.endsWith("/")) { serviceUrl = serviceUrl + "/"; } newServiceUrls.add(serviceUrl); } return newServiceUrls; } @Override public String readSource() throws Exception { return fetchStringSourceFromEurekaMetadata(this.appId, this.instanceId, this.serviceUrls, ruleKey); } private String fetchStringSourceFromEurekaMetadata(String appId, String instanceId, List serviceUrls, String ruleKey) throws Exception { List shuffleUrls = new ArrayList<>(serviceUrls.size()); shuffleUrls.addAll(serviceUrls); Collections.shuffle(shuffleUrls); for (int i = 0; i < shuffleUrls.size(); i++) { String serviceUrl = shuffleUrls.get(i) + String.format("apps/%s/%s", appId, instanceId); HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(serviceUrl).openConnection(); conn.addRequestProperty("Accept", "application/json;charset=utf-8"); conn.setConnectTimeout(connectTimeoutMills); conn.setReadTimeout(readTimeoutMills); conn.setRequestMethod("GET"); conn.setDoOutput(true); conn.connect(); RecordLog.debug("[EurekaDataSource] Request from eureka server: " + serviceUrl); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { String s = toString(conn.getInputStream()); String ruleString = JSON.parseObject(s) .getJSONObject("instance") .getJSONObject("metadata") .getString(ruleKey); return ruleString; } RecordLog.warn("[EurekaDataSource] Warn: retrying on another server if available " + "due to response code: {}, response message: {}", conn.getResponseCode(), toString(conn.getErrorStream())); } catch (Exception e) { try { if (conn != null) { RecordLog.warn("[EurekaDataSource] Warn: failed to request " + conn.getURL() + " from " + InetAddress.getByName(conn.getURL().getHost()).getHostAddress(), e); } } catch (Exception e1) { RecordLog.warn("[EurekaDataSource] Warn: failed to request ", e1); //ignore } RecordLog.warn("[EurekaDataSource] Warn: failed to request,retrying on another server if available"); } finally { if (conn != null) { conn.disconnect(); } } } throw new EurekaMetadataFetchException("Can't get any data"); } public static class EurekaMetadataFetchException extends Exception { public EurekaMetadataFetchException(String message) { super(message); } } private String toString(InputStream input) throws IOException { if (input == null) { return null; } InputStreamReader inputStreamReader = new InputStreamReader(input, "utf-8"); CharArrayWriter sw = new CharArrayWriter(); copy(inputStreamReader, sw); return sw.toString(); } private 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; } } ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/src/test/java/com/alibaba/csp/sentinel/datasource/eureka/EurekaDataSourceTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.eureka; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; /** * @author liyang */ @RunWith(SpringRunner.class) @EnableEurekaServer @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class EurekaDataSourceTest { private static final String SENTINEL_KEY = "sentinel-rules"; @Value("${server.port}") private int port; @Value("${eureka.instance.appname}") private String appname; @Value("${eureka.instance.instance-id}") private String instanceId; @Test public void testEurekaDataSource() throws Exception { String url = "http://localhost:" + port + "/eureka"; EurekaDataSource> eurekaDataSource = new EurekaDataSource(appname, instanceId, Arrays.asList(url) , SENTINEL_KEY, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); FlowRuleManager.register2Property(eurekaDataSource.getProperty()); await().timeout(15, TimeUnit.SECONDS) .until(new Callable() { @Override public Boolean call() throws Exception { return FlowRuleManager.getRules().size() > 0; } }); Assert.assertTrue(FlowRuleManager.getRules().size() > 0); } } ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/src/test/java/com/alibaba/csp/sentinel/datasource/eureka/SimpleSpringApplication.java ================================================ /* * Copyright (C) 2018 the original author or 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. */ package com.alibaba.csp.sentinel.datasource.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author liyang */ @SpringBootApplication public class SimpleSpringApplication { public static void main(String[] args) { SpringApplication.run(SimpleSpringApplication.class); } } ================================================ FILE: sentinel-extension/sentinel-datasource-eureka/src/test/resources/application.yml ================================================ server: port: 8761 eureka: instance: instance-id: instance-0 appname: testapp metadata-map: sentinel-rules: "[{'clusterMode':false,'controlBehavior':0,'count':20.0,'grade':1,'limitApp':'default','maxQueueingTimeMs':500,'resource':'resource-demo-name','strategy':0,'warmUpPeriodSec':10}]" client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:8761/eureka/ ================================================ FILE: sentinel-extension/sentinel-datasource-extension/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-extension ${revision} ../pom.xml sentinel-datasource-extension jar com.alibaba.csp sentinel-core junit junit test ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/AbstractDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.SentinelProperty; /** * The abstract readable data source provides basic functionality for loading and parsing config. * * @param source data type * @param target data type * @author Carpenter Lee * @author Eric Zhao */ public abstract class AbstractDataSource implements ReadableDataSource { protected final Converter parser; protected final SentinelProperty property; public AbstractDataSource(Converter parser) { if (parser == null) { throw new IllegalArgumentException("parser can't be null"); } this.parser = parser; this.property = new DynamicSentinelProperty(); } @Override public T loadConfig() throws Exception { return loadConfig(readSource()); } public T loadConfig(S conf) throws Exception { T value = parser.convert(conf); return value; } @Override public SentinelProperty getProperty() { return property; } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/AutoRefreshDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; /** * A {@link ReadableDataSource} automatically fetches the backend data. * * @param source data type * @param target data type * @author Carpenter Lee */ public abstract class AutoRefreshDataSource extends AbstractDataSource { private ScheduledExecutorService service; protected long recommendRefreshMs = 3000; public AutoRefreshDataSource(Converter configParser) { super(configParser); startTimerService(); } public AutoRefreshDataSource(Converter configParser, final long recommendRefreshMs) { super(configParser); if (recommendRefreshMs <= 0) { throw new IllegalArgumentException("recommendRefreshMs must > 0, but " + recommendRefreshMs + " get"); } this.recommendRefreshMs = recommendRefreshMs; startTimerService(); } @SuppressWarnings("PMD.ThreadPoolCreationRule") private void startTimerService() { service = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-datasource-auto-refresh-task", true)); service.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { if (!isModified()) { return; } T newValue = loadConfig(); getProperty().updateValue(newValue); } catch (Throwable e) { RecordLog.info("loadConfig exception", e); } } }, recommendRefreshMs, recommendRefreshMs, TimeUnit.MILLISECONDS); } @Override public void close() throws Exception { if (service != null) { service.shutdownNow(); service = null; } } protected boolean isModified() { return true; } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/Converter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; /** * Convert an object from source type {@code S} to target type {@code T}. * * @author leyou * @author Eric Zhao */ public interface Converter { /** * Convert {@code source} to the target type. * * @param source the source object * @return the target object */ T convert(S source); } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/EmptyDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import com.alibaba.csp.sentinel.property.NoOpSentinelProperty; import com.alibaba.csp.sentinel.property.SentinelProperty; /** * A {@link ReadableDataSource} based on nothing. {@link EmptyDataSource#getProperty()} will always return the same cached * {@link SentinelProperty} that doing nothing. *
          * This class is used when we want to use default settings instead of configs from the {@link ReadableDataSource}. * * @author leyou */ public final class EmptyDataSource implements ReadableDataSource { public static final ReadableDataSource EMPTY_DATASOURCE = new EmptyDataSource(); private static final SentinelProperty PROPERTY = new NoOpSentinelProperty(); private EmptyDataSource() { } @Override public Object loadConfig() throws Exception { return null; } @Override public Object readSource() throws Exception { return null; } @Override public SentinelProperty getProperty() { return PROPERTY; } @Override public void close() throws Exception { } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/FileInJarReadableDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** *

          * A {@link ReadableDataSource} based on jar file. This class can only read file initially when it loads file. *

          *

          * Limitations: Default read buffer size is 1 MB, while max allowed buffer size is 4MB. * File size should not exceed the buffer size, or exception will be thrown. Default charset is UTF-8. *

          * * @author dingq * @author Eric Zhao * @since 1.6.0 */ public class FileInJarReadableDataSource extends AbstractDataSource { private static final int MAX_SIZE = 1024 * 1024 * 4; private static final int DEFAULT_BUF_SIZE = 1024 * 1024; private static final Charset DEFAULT_CHARSET = Charset.forName("utf-8"); private final Charset charset; private final String jarName; private final String fileInJarName; private byte[] buf; private JarEntry jarEntry; private JarFile jarFile; /** * @param jarName the jar to read * @param fileInJarName the file in jar to read * @param configParser the config decoder (parser) * @throws IOException if IO failure occurs */ public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter configParser) throws IOException { this(jarName, fileInJarName, configParser, DEFAULT_BUF_SIZE, DEFAULT_CHARSET); } public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter configParser, int bufSize) throws IOException { this(jarName, fileInJarName, configParser, bufSize, DEFAULT_CHARSET); } public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter configParser, Charset charset) throws IOException { this(jarName, fileInJarName, configParser, DEFAULT_BUF_SIZE, charset); } public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter configParser, int bufSize, Charset charset) throws IOException { super(configParser); AssertUtil.assertNotBlank(jarName, "jarName cannot be blank"); AssertUtil.assertNotBlank(fileInJarName, "fileInJarName cannot be blank"); if (bufSize <= 0 || bufSize > MAX_SIZE) { throw new IllegalArgumentException("bufSize must between (0, " + MAX_SIZE + "], but " + bufSize + " get"); } AssertUtil.notNull(charset, "charset can't be null"); this.buf = new byte[bufSize]; this.charset = charset; this.jarName = jarName; this.fileInJarName = fileInJarName; initializeJar(); firstLoad(); } @Override public String readSource() throws Exception { if (null == jarEntry) { // Will throw FileNotFoundException later. RecordLog.warn(String.format("[FileInJarReadableDataSource] File does not exist: %s", jarFile.getName())); } try (InputStream inputStream = jarFile.getInputStream(jarEntry)) { if (inputStream.available() > buf.length) { throw new IllegalStateException(String.format("Size of file <%s> exceeds the bufSize (%d): %d", jarFile.getName(), buf.length, inputStream.available())); } int len = inputStream.read(buf); return new String(buf, 0, len, charset); } } private void firstLoad() { try { T newValue = loadConfig(); getProperty().updateValue(newValue); } catch (Throwable e) { RecordLog.warn("[FileInJarReadableDataSource] Error when loading config", e); } } @Override public void close() throws Exception { buf = null; } private void initializeJar() throws IOException { this.jarFile = new JarFile(jarName); this.jarEntry = jarFile.getJarEntry(fileInJarName); } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/FileRefreshableDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import com.alibaba.csp.sentinel.log.RecordLog; /** *

          * A {@link ReadableDataSource} based on file. This class will automatically * fetches the backend file every isModified period. *

          *

          * Limitations: Default read buffer size is 1 MB. If file size is greater than * buffer size, exceeding bytes will be ignored. Default charset is UTF-8. *

          * * @author Carpenter Lee * @author Eric Zhao */ public class FileRefreshableDataSource extends AutoRefreshDataSource { private static final int MAX_SIZE = 1024 * 1024 * 4; private static final long DEFAULT_REFRESH_MS = 3000; private static final int DEFAULT_BUF_SIZE = 1024 * 1024; private static final Charset DEFAULT_CHAR_SET = Charset.forName("utf-8"); private byte[] buf; private final Charset charset; private final File file; private long lastModified = 0L; /** * Create a file based {@link ReadableDataSource} whose read buffer size is * 1MB, charset is UTF8, and read interval is 3 seconds. * * @param file the file to read * @param configParser the config decoder (parser) */ public FileRefreshableDataSource(File file, Converter configParser) throws FileNotFoundException { this(file, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, DEFAULT_CHAR_SET); } public FileRefreshableDataSource(String fileName, Converter configParser) throws FileNotFoundException { this(new File(fileName), configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, DEFAULT_CHAR_SET); } public FileRefreshableDataSource(File file, Converter configParser, int bufSize) throws FileNotFoundException { this(file, configParser, DEFAULT_REFRESH_MS, bufSize, DEFAULT_CHAR_SET); } public FileRefreshableDataSource(File file, Converter configParser, Charset charset) throws FileNotFoundException { this(file, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, charset); } public FileRefreshableDataSource(File file, Converter configParser, long recommendRefreshMs, int bufSize, Charset charset) throws FileNotFoundException { super(configParser, recommendRefreshMs); if (bufSize <= 0 || bufSize > MAX_SIZE) { throw new IllegalArgumentException("bufSize must between (0, " + MAX_SIZE + "], but " + bufSize + " get"); } if (file == null || file.isDirectory()) { throw new IllegalArgumentException("File can't be null or a directory"); } if (charset == null) { throw new IllegalArgumentException("charset can't be null"); } this.buf = new byte[bufSize]; this.file = file; this.charset = charset; // If the file does not exist, the last modified will be 0. this.lastModified = file.lastModified(); firstLoad(); } private void firstLoad() { try { T newValue = loadConfig(); getProperty().updateValue(newValue); } catch (Throwable e) { RecordLog.info("loadConfig exception", e); } } @Override public String readSource() throws Exception { if (!file.exists()) { // Will throw FileNotFoundException later. RecordLog.warn(String.format("[FileRefreshableDataSource] File does not exist: %s", file.getAbsolutePath())); } FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); FileChannel channel = inputStream.getChannel(); if (channel.size() > buf.length) { throw new IllegalStateException(file.getAbsolutePath() + " file size=" + channel.size() + ", is bigger than bufSize=" + buf.length + ". Can't read"); } int len = inputStream.read(buf); return new String(buf, 0, len, charset); } finally { if (inputStream != null) { try { inputStream.close(); } catch (Exception ignore) { } } } } @Override protected boolean isModified() { long curLastModified = file.lastModified(); if (curLastModified != this.lastModified) { this.lastModified = curLastModified; return true; } return false; } @Override public void close() throws Exception { super.close(); buf = null; } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/FileWritableDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import java.io.File; import java.io.FileOutputStream; import java.nio.charset.Charset; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.alibaba.csp.sentinel.log.RecordLog; /** * A {@link WritableDataSource} based on file. * * @param data type * @author Eric Zhao * @since 0.2.0 */ public class FileWritableDataSource implements WritableDataSource { private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private final Converter configEncoder; private final File file; private final Charset charset; private final Lock lock = new ReentrantLock(true); public FileWritableDataSource(String filePath, Converter configEncoder) { this(new File(filePath), configEncoder); } public FileWritableDataSource(File file, Converter configEncoder) { this(file, configEncoder, DEFAULT_CHARSET); } public FileWritableDataSource(File file, Converter configEncoder, Charset charset) { if (file == null || file.isDirectory()) { throw new IllegalArgumentException("Bad file"); } if (configEncoder == null) { throw new IllegalArgumentException("Config encoder cannot be null"); } if (charset == null) { throw new IllegalArgumentException("Charset cannot be null"); } this.configEncoder = configEncoder; this.file = file; this.charset = charset; } @Override public void write(T value) throws Exception { lock.lock(); try { String convertResult = configEncoder.convert(value); FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(file); byte[] bytesArray = convertResult.getBytes(charset); RecordLog.info("[FileWritableDataSource] Writing to file {}: {}", file, convertResult); outputStream.write(bytesArray); outputStream.flush(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (Exception ignore) { // nothing } } } } finally { lock.unlock(); } } @Override public void close() throws Exception { // Nothing } } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/ReadableDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; import com.alibaba.csp.sentinel.property.SentinelProperty; /** * The readable data source is responsible for retrieving configs (read-only). * * @param source data type * @param target data type * @author leyou * @author Eric Zhao */ public interface ReadableDataSource { /** * Load data data source as the target type. * * @return the target data. * @throws Exception IO or other error occurs */ T loadConfig() throws Exception; /** * Read original data from the data source. * * @return the original data. * @throws Exception IO or other error occurs */ S readSource() throws Exception; /** * Get {@link SentinelProperty} of the data source. * * @return the property. */ SentinelProperty getProperty(); /** * Close the data source. * * @throws Exception IO or other error occurs */ void close() throws Exception; } ================================================ FILE: sentinel-extension/sentinel-datasource-extension/src/main/java/com/alibaba/csp/sentinel/datasource/WritableDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource; /** * Interface of writable data source support. * * @author Eric Zhao * @since 0.2.0 */ public interface WritableDataSource { /** * Write the {@code value} to the data source. * * @param value value to write * @throws Exception IO or other error occurs */ void write(T value) throws Exception; /** * Close the data source. * * @throws Exception IO or other error occurs */ void close() throws Exception; } ================================================ FILE: sentinel-extension/sentinel-datasource-nacos/README.md ================================================ # Sentinel DataSource Nacos Sentinel DataSource Nacos provides integration with [Nacos](http://nacos.io) so that Nacos can be the dynamic rule data source of Sentinel. To use Sentinel DataSource Nacos, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-nacos x.y.z ``` Then you can create an `NacosDataSource` and register to rule managers. For instance: ```java // remoteAddress is the address of Nacos // groupId and dataId are concepts of Nacos ReadableDataSource> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, source -> JSON.parseObject(source, new TypeReference>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); ``` We've also provided an example: [sentinel-demo-nacos-datasource](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-nacos-datasource). ================================================ FILE: sentinel-extension/sentinel-datasource-nacos/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-nacos jar 1.4.2 com.alibaba.csp sentinel-datasource-extension com.alibaba.nacos nacos-client ${nacos.version} ================================================ FILE: sentinel-extension/sentinel-datasource-nacos/src/main/java/com/alibaba/csp/sentinel/datasource/nacos/NacosDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.nacos; import java.util.Properties; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; 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; /** * A read-only {@code DataSource} with Nacos backend. When the data in Nacos backend has been modified, * Nacos will automatically push the new value so that the dynamic configuration can be real-time. * * @author Eric Zhao */ public class NacosDataSource extends AbstractDataSource { private static final int DEFAULT_TIMEOUT = 3000; /** * Single-thread pool. Once the thread pool is blocked, we throw up the old task. */ private final ExecutorService pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(1), new NamedThreadFactory("sentinel-nacos-ds-update", true), new ThreadPoolExecutor.DiscardOldestPolicy()); private final Listener configListener; private final String groupId; private final String dataId; private final Properties properties; /** * Note: The Nacos config might be null if its initialization failed. */ private ConfigService configService = null; /** * Constructs an read-only DataSource with Nacos backend. * * @param serverAddr server address of Nacos, cannot be empty * @param groupId group ID, cannot be empty * @param dataId data ID, cannot be empty * @param parser customized data parser, cannot be empty */ public NacosDataSource(final String serverAddr, final String groupId, final String dataId, Converter parser) { this(NacosDataSource.buildProperties(serverAddr), groupId, dataId, parser); } /** * * @param properties properties for construct {@link ConfigService} using {@link NacosFactory#createConfigService(Properties)} * @param groupId group ID, cannot be empty * @param dataId data ID, cannot be empty * @param parser customized data parser, cannot be empty */ public NacosDataSource(final Properties properties, final String groupId, final String dataId, Converter parser) { super(parser); if (StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) { throw new IllegalArgumentException(String.format("Bad argument: groupId=[%s], dataId=[%s]", groupId, dataId)); } AssertUtil.notNull(properties, "Nacos properties must not be null, you could put some keys from PropertyKeyConst"); this.groupId = groupId; this.dataId = dataId; this.properties = properties; this.configListener = new Listener() { @Override public Executor getExecutor() { return pool; } @Override public void receiveConfigInfo(final String configInfo) { RecordLog.info("[NacosDataSource] New property value received for (properties: {}) (dataId: {}, groupId: {}): {}", properties, dataId, groupId, configInfo); T newValue = NacosDataSource.this.parser.convert(configInfo); // Update the new value to the property. getProperty().updateValue(newValue); } }; initNacosListener(); loadInitialConfig(); } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn("[NacosDataSource] WARN: initial config is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[NacosDataSource] Error when loading initial config", ex); } } private void initNacosListener() { try { this.configService = NacosFactory.createConfigService(this.properties); // Add config listener. configService.addListener(dataId, groupId, configListener); } catch (Exception e) { RecordLog.warn("[NacosDataSource] Error occurred when initializing Nacos data source", e); e.printStackTrace(); } } @Override public String readSource() throws Exception { if (configService == null) { throw new IllegalStateException("Nacos config service has not been initialized or error occurred"); } return configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT); } @Override public void close() { if (configService != null) { configService.removeListener(dataId, groupId, configListener); try { configService.shutDown(); } catch (Exception e) { RecordLog.warn("[NacosDataSource] Error occurred when closing Nacos data source", e); e.printStackTrace(); } } pool.shutdownNow(); } private static Properties buildProperties(String serverAddr) { Properties properties = new Properties(); properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr); return properties; } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/README.md ================================================ # Sentinel DataSource Redis Sentinel DataSource Redis provides integration with Redis. The data source leverages Redis pub-sub feature to implement push model (listener). The data source uses [Lettuce](https://lettuce.io/) as the Redis client, which requires JDK 1.8 or later. > **NOTE**: Currently we do not support Redis Cluster now. ## Usage To use Sentinel DataSource Redis, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-redis x.y.z ``` Then you can create an `RedisDataSource` and register to rule managers. For instance: ```java ReadableDataSource> redisDataSource = new RedisDataSource>(redisConnectionConfig, ruleKey, channel, flowConfigParser); FlowRuleManager.register2Property(redisDataSource.getProperty()); ``` - `redisConnectionConfig`: use `RedisConnectionConfig` class to build your Redis connection config - `ruleKey`: the rule persistence key of a Redis String - `channel`: the channel to subscribe You can also create multi data sources to subscribe for different rule type. Note that the data source first loads initial rules from a Redis String (provided `ruleKey`) during initialization. So for consistency, users should publish the value and save the value to the `ruleKey` simultaneously like this (using Redis transaction): ```plaintext MULTI SET ruleKey value PUBLISH channel value EXEC ``` An example using Lettuce Redis client: ```java public void pushRules(List rules, Converter, String> encoder) { StatefulRedisPubSubConnection connection = client.connectPubSub(); RedisPubSubCommands subCommands = connection.sync(); String value = encoder.convert(rules); subCommands.multi(); subCommands.set(ruleKey, value); subCommands.publish(ruleChannel, value); subCommands.exec(); } ``` Transaction can be handled in Redis Cluster when just using the same key. An example using Lettuce Redis Cluster client: ```java public void pushRules(List rules, Converter, String> encoder) { RedisAdvancedClusterCommands subCommands = client.connect().sync(); int slot = SlotHash.getSlot(ruleKey); NodeSelection nodes = subCommands.nodes((n)->n.hasSlot(slot)); RedisCommands commands = nodes.commands(0); String value = encoder.convert(rules); commands.multi(); commands.set(ruleKey, value); commands.publish(channel, value); commands.exec(); } ``` ## How to build RedisConnectionConfig ### Build with Redis standalone mode ```java RedisConnectionConfig config = RedisConnectionConfig.builder() .withHost("localhost") .withPort(6379) .withPassword("pwd") .withDataBase(2) .build(); ``` ### Build with Redis Sentinel mode ```java RedisConnectionConfig config = RedisConnectionConfig.builder() .withRedisSentinel("redisSentinelServer1",5000) .withRedisSentinel("redisSentinelServer2",5001) .withRedisSentinelMasterId("redisSentinelMasterId").build(); ``` ### Build with Redis Cluster mode ```java RedisConnectionConfig config = RedisConnectionConfig.builder() .withRedisCluster("redisSentinelServer1",5000) .withRedisCluster("redisSentinelServer2",5001).build(); ``` ================================================ FILE: sentinel-extension/sentinel-datasource-redis/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-redis jar 1.8 1.8 5.3.1.RELEASE 0.1.6 com.alibaba.csp sentinel-datasource-extension io.lettuce lettuce-core ${lettuce.version} junit junit test org.awaitility awaitility test com.alibaba fastjson test ai.grakn redis-mock ${redis.mock.version} test ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/RedisDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.SslOptions; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.ClusterClientOptions; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import io.lettuce.core.pubsub.RedisPubSubAdapter; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; import java.io.File; import java.time.Duration; import java.util.ArrayList; import java.util.List; /** *

          * A read-only {@code DataSource} with Redis backend. *

          *

          * The data source first loads initial rules from a Redis String during initialization. * Then the data source subscribe from specific channel. When new rules is published to the channel, * the data source will observe the change in realtime and update to memory. *

          *

          * Note that for consistency, users should publish the value and save the value to the ruleKey simultaneously * like this (using Redis transaction): *

           *  MULTI
           *  SET ruleKey value
           *  PUBLISH channel value
           *  EXEC
           * 
          *

          * * @author tiger */ public class RedisDataSource extends AbstractDataSource { private final RedisClient redisClient; private final RedisClusterClient redisClusterClient; private final String ruleKey; /** * Constructor of {@code RedisDataSource}. * * @param connectionConfig Redis connection config * @param ruleKey data key in Redis * @param channel channel to subscribe in Redis * @param parser customized data parser, cannot be empty */ public RedisDataSource(RedisConnectionConfig connectionConfig, String ruleKey, String channel, Converter parser) { super(parser); AssertUtil.notNull(connectionConfig, "Redis connection config can not be null"); AssertUtil.notEmpty(ruleKey, "Redis ruleKey can not be empty"); AssertUtil.notEmpty(channel, "Redis subscribe channel can not be empty"); if (connectionConfig.getRedisClusters().size() == 0) { this.redisClient = getRedisClient(connectionConfig); this.redisClusterClient = null; } else { this.redisClusterClient = getRedisClusterClient(connectionConfig); this.redisClient = null; } this.ruleKey = ruleKey; loadInitialConfig(); subscribeFromChannel(channel); } /** * init SslOptions, support jks or pem format * * @param connectionConfig Redis connection config * @return a new SslOptions */ private SslOptions initSslOptions(RedisConnectionConfig connectionConfig) { if (!connectionConfig.isSslEnable()){ return null; } SslOptions.Builder sslOptionsBuilder = SslOptions.builder(); if (connectionConfig.getTrustedCertificatesPath() != null){ if (connectionConfig.getTrustedCertificatesPath().endsWith(".jks")){ // if the value is end with .jks,think it is java key store format,to invoke truststore method sslOptionsBuilder.truststore( new File(connectionConfig.getTrustedCertificatesPath()), connectionConfig.getTrustedCertificatesJksPassword() ); } else { // if the value is not end with .jks,think it is pem format,to invoke trustManager method sslOptionsBuilder.trustManager(new File(connectionConfig.getTrustedCertificatesPath())); } } if (connectionConfig.getKeyCertChainFilePath() != null || connectionConfig.getKeyFilePath() != null) { if (connectionConfig.getKeyFilePath().endsWith(".jks")){ sslOptionsBuilder.keystore( new File(connectionConfig.getKeyCertChainFilePath()), connectionConfig.getKeyFilePassword() == null ? null : connectionConfig.getKeyFilePassword().toCharArray() ); } else { sslOptionsBuilder.keyManager( new File(connectionConfig.getKeyCertChainFilePath()), new File(connectionConfig.getKeyFilePath()), connectionConfig.getKeyFilePassword() == null ? null : connectionConfig.getKeyFilePassword().toCharArray() ); } } return sslOptionsBuilder.build(); } /** * Build Redis client fromm {@code RedisConnectionConfig}. * * @return a new {@link RedisClient} */ private RedisClient getRedisClient(RedisConnectionConfig connectionConfig) { RedisClient redisClient; if (connectionConfig.getRedisSentinels().size() == 0) { RecordLog.info("[RedisDataSource] Creating stand-alone mode Redis client"); redisClient = getRedisStandaloneClient(connectionConfig); } else { RecordLog.info("[RedisDataSource] Creating Redis Sentinel mode Redis client"); redisClient = getRedisSentinelClient(connectionConfig); } SslOptions sslOptions = initSslOptions(connectionConfig); if (sslOptions != null){ redisClient.setOptions( ClusterClientOptions.builder().sslOptions(sslOptions).build() ); } return redisClient; } private RedisClusterClient getRedisClusterClient(RedisConnectionConfig connectionConfig) { char[] password = connectionConfig.getPassword(); String clientName = connectionConfig.getClientName(); //If any uri is successful for connection, the others are not tried anymore List redisUris = new ArrayList<>(); for (RedisConnectionConfig config : connectionConfig.getRedisClusters()) { RedisURI.Builder clusterRedisUriBuilder = RedisURI.builder(); clusterRedisUriBuilder.withHost(config.getHost()) .withPort(config.getPort()) .withSsl(config.isSslEnable()) .withTimeout(Duration.ofMillis(connectionConfig.getTimeout())); //All redis nodes must have same password if (password != null) { clusterRedisUriBuilder.withPassword(connectionConfig.getPassword()); } redisUris.add(clusterRedisUriBuilder.build()); } RedisClusterClient redisClusterClient = RedisClusterClient.create(redisUris); SslOptions sslOptions = initSslOptions(connectionConfig); if (sslOptions != null){ redisClusterClient.setOptions( ClusterClientOptions.builder().sslOptions(sslOptions).build() ); } return redisClusterClient; } private RedisClient getRedisStandaloneClient(RedisConnectionConfig connectionConfig) { char[] password = connectionConfig.getPassword(); String clientName = connectionConfig.getClientName(); RedisURI.Builder redisUriBuilder = RedisURI.builder(); redisUriBuilder.withHost(connectionConfig.getHost()) .withPort(connectionConfig.getPort()) .withDatabase(connectionConfig.getDatabase()) .withSsl(connectionConfig.isSslEnable()) .withTimeout(Duration.ofMillis(connectionConfig.getTimeout())); if (password != null) { redisUriBuilder.withPassword(connectionConfig.getPassword()); } if (StringUtil.isNotEmpty(connectionConfig.getClientName())) { redisUriBuilder.withClientName(clientName); } return RedisClient.create(redisUriBuilder.build()); } private RedisClient getRedisSentinelClient(RedisConnectionConfig connectionConfig) { char[] password = connectionConfig.getPassword(); String clientName = connectionConfig.getClientName(); RedisURI.Builder sentinelRedisUriBuilder = RedisURI.builder(); for (RedisConnectionConfig config : connectionConfig.getRedisSentinels()) { sentinelRedisUriBuilder.withSentinel(config.getHost(), config.getPort()); } if (password != null) { sentinelRedisUriBuilder.withPassword(connectionConfig.getPassword()); } if (StringUtil.isNotEmpty(connectionConfig.getClientName())) { sentinelRedisUriBuilder.withClientName(clientName); } sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId()) .withSsl(connectionConfig.isSslEnable()) .withTimeout(Duration.ofMillis(connectionConfig.getTimeout())); return RedisClient.create(sentinelRedisUriBuilder.build()); } private void subscribeFromChannel(String channel) { RedisPubSubAdapter adapterListener = new DelegatingRedisPubSubListener(); if (redisClient != null) { StatefulRedisPubSubConnection pubSubConnection = redisClient.connectPubSub(); pubSubConnection.addListener(adapterListener); RedisPubSubCommands sync = pubSubConnection.sync(); sync.subscribe(channel); } else { StatefulRedisClusterPubSubConnection pubSubConnection = redisClusterClient.connectPubSub(); pubSubConnection.addListener(adapterListener); RedisPubSubCommands sync = pubSubConnection.sync(); sync.subscribe(channel); } } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn("[RedisDataSource] WARN: initial config is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[RedisDataSource] Error when loading initial config", ex); } } @Override public String readSource() { if (this.redisClient == null && this.redisClusterClient == null) { throw new IllegalStateException("Redis client or Redis Cluster client has not been initialized or error occurred"); } if (redisClient != null) { RedisCommands stringRedisCommands = redisClient.connect().sync(); return stringRedisCommands.get(ruleKey); } else { RedisAdvancedClusterCommands stringRedisCommands = redisClusterClient.connect().sync(); return stringRedisCommands.get(ruleKey); } } @Override public void close() { if (redisClient != null) { redisClient.shutdown(); } else { redisClusterClient.shutdown(); } } private class DelegatingRedisPubSubListener extends RedisPubSubAdapter { DelegatingRedisPubSubListener() { } @Override public void message(String channel, String message) { RecordLog.info("[RedisDataSource] New property value received for channel {}: {}", channel, message); getProperty().updateValue(parser.convert(message)); } } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisConnectionConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis.config; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.*; /** * This class provide a builder to build redis client connection config. * * @author tiger */ public class RedisConnectionConfig { /** * The default redisSentinel port. */ public static final int DEFAULT_SENTINEL_PORT = 26379; /** * The default redisCluster port. */ public static final int DEFAULT_CLUSTER_PORT = 6379; /** * The default redis port. */ public static final int DEFAULT_REDIS_PORT = 6379; /** * Default timeout: 60 sec */ public static final long DEFAULT_TIMEOUT_MILLISECONDS = 60 * 1000; private String host; private String redisSentinelMasterId; private int port; private boolean sslEnable; private String trustedCertificatesPath; private String trustedCertificatesJksPassword; private String keyCertChainFilePath; private String keyFilePath; private String keyFilePassword; private int database; private String clientName; private char[] password; private long timeout = DEFAULT_TIMEOUT_MILLISECONDS; private final List redisSentinels = new ArrayList(); private final List redisClusters = new ArrayList(); /** * Default empty constructor. */ public RedisConnectionConfig() { } /** * Constructor with host/port and timeout. * * @param host the host * @param port the port * @param timeout timeout value . unit is mill seconds */ public RedisConnectionConfig(String host, int port, long timeout) { AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.notNull(timeout, "Timeout duration must not be null"); AssertUtil.isTrue(timeout >= 0, "Timeout duration must be greater or equal to zero"); setHost(host); setPort(port); setTimeout(timeout); } /** * Returns a new {@link RedisConnectionConfig.Builder} to construct a {@link RedisConnectionConfig}. * * @return a new {@link RedisConnectionConfig.Builder} to construct a {@link RedisConnectionConfig}. */ public static RedisConnectionConfig.Builder builder() { return new RedisConnectionConfig.Builder(); } /** * Returns the host. * * @return the host. */ public String getHost() { return host; } /** * Sets the Redis host. * * @param host the host */ public void setHost(String host) { this.host = host; } /** * Returns the Sentinel Master Id. * * @return the Sentinel Master Id. */ public String getRedisSentinelMasterId() { return redisSentinelMasterId; } /** * Sets the Sentinel Master Id. * * @param redisSentinelMasterId the Sentinel Master Id. */ public void setRedisSentinelMasterId(String redisSentinelMasterId) { this.redisSentinelMasterId = redisSentinelMasterId; } /** * Returns the Redis port. * * @return the Redis port */ public int getPort() { return port; } /** * Sets the Redis port. Defaults to {@link #DEFAULT_REDIS_PORT}. * * @param port the Redis port */ public void setPort(int port) { this.port = port; } /** * Returns the password. * * @return the password */ public char[] getPassword() { return password; } /** * Sets the password. Use empty string to skip authentication. * * @param password the password, must not be {@literal null}. */ public void setPassword(String password) { AssertUtil.notNull(password, "Password must not be null"); this.password = password.toCharArray(); } /** * Sets the password. Use empty char array to skip authentication. * * @param password the password, must not be {@literal null}. */ public void setPassword(char[] password) { AssertUtil.notNull(password, "Password must not be null"); this.password = Arrays.copyOf(password, password.length); } /** * Returns the command timeout for synchronous command execution. * * @return the Timeout */ public long getTimeout() { return timeout; } /** * Sets the command timeout for synchronous command execution. * * @param timeout the command timeout for synchronous command execution. */ public void setTimeout(Long timeout) { AssertUtil.notNull(timeout, "Timeout must not be null"); AssertUtil.isTrue(timeout >= 0, "Timeout must be greater or equal 0"); this.timeout = timeout; } /** * Returns the Redis database number. Databases are only available for Redis Standalone and Redis Master/Slave. * * @return database */ public int getDatabase() { return database; } /** * Sets the Redis database number. Databases are only available for Redis Standalone and Redis Master/Slave. * * @param database the Redis database number. */ public void setDatabase(int database) { AssertUtil.isTrue(database >= 0, "Invalid database number: " + database); this.database = database; } /** * Returns the client name. * * @return */ public String getClientName() { return clientName; } /** * Sets the client name to be applied on Redis connections. * * @param clientName the client name. */ public void setClientName(String clientName) { this.clientName = clientName; } /** * @return the list of {@link RedisConnectionConfig Redis Sentinel URIs}. */ public List getRedisSentinels() { return redisSentinels; } /** * @return the list of {@link RedisConnectionConfig Redis Cluster URIs}. */ public List getRedisClusters() { return redisClusters; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" ["); if (host != null) { sb.append("host='").append(host).append('\''); sb.append(", port=").append(port); } if (redisSentinelMasterId != null) { sb.append("redisSentinels=").append(getRedisSentinels()); sb.append(", redisSentinelMasterId=").append(redisSentinelMasterId); } if (redisClusters.size() > 0) { sb.append("redisClusters=").append(getRedisClusters()); } sb.append(']'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof RedisConnectionConfig)) { return false; } RedisConnectionConfig redisURI = (RedisConnectionConfig)o; if (port != redisURI.port) { return false; } if (database != redisURI.database) { return false; } if (host != null ? !host.equals(redisURI.host) : redisURI.host != null) { return false; } if (redisSentinelMasterId != null ? !redisSentinelMasterId.equals(redisURI.redisSentinelMasterId) : redisURI.redisSentinelMasterId != null) { return false; } if (redisClusters != null ? !redisClusters.equals(redisURI.redisClusters) : redisURI.redisClusters != null) { return false; } return !(redisSentinels != null ? !redisSentinels.equals(redisURI.redisSentinels) : redisURI.redisSentinels != null); } @Override public int hashCode() { int result = host != null ? host.hashCode() : 0; result = 31 * result + (redisSentinelMasterId != null ? redisSentinelMasterId.hashCode() : 0); result = 31 * result + port; result = 31 * result + database; result = 31 * result + (redisSentinels != null ? redisSentinels.hashCode() : 0); result = 31 * result + (redisClusters != null ? redisClusters.hashCode() : 0); return result; } /** * Builder for Redis RedisConnectionConfig. */ public static class Builder { private String host; private String redisSentinelMasterId; private int port; private int database; private String clientName; private char[] password; private boolean sslEnable; private String trustedCertificatesPath; private String trustedCertificatesJksPassword; private String keyCertChainFilePath; private String keyFilePath; private String keyFilePassword; private long timeout = DEFAULT_TIMEOUT_MILLISECONDS; private final List redisSentinels = new ArrayList(); private final List redisClusters = new ArrayList(); private Builder() { } /** * Set Redis host. Creates a new builder. * * @param host the host name * @return New builder with Redis host/port. */ public static RedisConnectionConfig.Builder redis(String host) { return redis(host, DEFAULT_REDIS_PORT); } /** * Set Redis host and port. Creates a new builder * * @param host the host name * @param port the port * @return New builder with Redis host/port. */ public static RedisConnectionConfig.Builder redis(String host, int port) { AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); Builder builder = RedisConnectionConfig.builder(); return builder.withHost(host).withPort(port); } /** * Set Sentinel host. Creates a new builder. * * @param host the host name * @return New builder with Sentinel host/port. */ public static RedisConnectionConfig.Builder redisSentinel(String host) { AssertUtil.notEmpty(host, "Host must not be empty"); RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); return builder.withRedisSentinel(host); } /** * Set Sentinel host and port. Creates a new builder. * * @param host the host name * @param port the port * @return New builder with Sentinel host/port. */ public static RedisConnectionConfig.Builder redisSentinel(String host, int port) { AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); return builder.withRedisSentinel(host, port); } /** * Set Sentinel host and master id. Creates a new builder. * * @param host the host name * @param masterId redisSentinel master id * @return New builder with Sentinel host/port. */ public static RedisConnectionConfig.Builder redisSentinel(String host, String masterId) { return redisSentinel(host, DEFAULT_SENTINEL_PORT, masterId); } /** * Set Sentinel host, port and master id. Creates a new builder. * * @param host the host name * @param port the port * @param masterId redisSentinel master id * @return New builder with Sentinel host/port. */ public static RedisConnectionConfig.Builder redisSentinel(String host, int port, String masterId) { AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); return builder.withSentinelMasterId(masterId).withRedisSentinel(host, port); } /** * Add a withRedisSentinel host to the existing builder. * * @param host the host name * @return the builder */ public RedisConnectionConfig.Builder withRedisSentinel(String host) { return withRedisSentinel(host, DEFAULT_SENTINEL_PORT); } /** * Add a withRedisSentinel host/port to the existing builder. * * @param host the host name * @param port the port * @return the builder */ public RedisConnectionConfig.Builder withRedisSentinel(String host, int port) { AssertUtil.assertState(this.host == null, "Cannot use with Redis mode."); AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); redisSentinels.add(RedisHostAndPort.of(host, port)); return this; } /** * Set Cluster host. Creates a new builder. * * @param host the host name * @return New builder with Cluster host/port. */ public static RedisConnectionConfig.Builder redisCluster(String host) { AssertUtil.notEmpty(host, "Host must not be empty"); RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); return builder.withRedisCluster(host); } /** * Set Cluster host and port. Creates a new builder. * * @param host the host name * @param port the port * @return New builder with Cluster host/port. */ public static RedisConnectionConfig.Builder redisCluster(String host, int port) { AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); return builder.withRedisCluster(host, port); } /** * Add a withRedisCluster host to the existing builder. * * @param host the host name * @return the builder */ public RedisConnectionConfig.Builder withRedisCluster(String host) { return withRedisCluster(host, DEFAULT_CLUSTER_PORT); } /** * Add a withRedisCluster host/port to the existing builder. * * @param host the host name * @param port the port * @return the builder */ public RedisConnectionConfig.Builder withRedisCluster(String host, int port) { AssertUtil.assertState(this.host == null, "Cannot use with Redis mode."); AssertUtil.notEmpty(host, "Host must not be empty"); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); redisClusters.add(RedisHostAndPort.of(host, port)); return this; } /** * Adds host information to the builder. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param host the port * @return the builder */ public RedisConnectionConfig.Builder withHost(String host) { AssertUtil.assertState(this.redisSentinels.isEmpty(), "Sentinels are non-empty. Cannot use in Sentinel mode."); AssertUtil.notEmpty(host, "Host must not be empty"); this.host = host; return this; } /** * Adds port information to the builder. Does only affect Redis URI, cannot be used with Sentinel connections. * * @param port the port * @return the builder */ public RedisConnectionConfig.Builder withPort(int port) { AssertUtil.assertState(this.host != null, "Host is null. Cannot use in Sentinel mode."); AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); this.port = port; return this; } /** * Configures the database number. * * @param database the database number * @return the builder */ public RedisConnectionConfig.Builder withDatabase(int database) { AssertUtil.isTrue(database >= 0, "Invalid database number: " + database); this.database = database; return this; } /** * Configures a client name. * * @param clientName the client name * @return the builder */ public RedisConnectionConfig.Builder withClientName(String clientName) { AssertUtil.notNull(clientName, "Client name must not be null"); this.clientName = clientName; return this; } /** * Configures authentication. * * @param password the password * @return the builder */ public RedisConnectionConfig.Builder withPassword(String password) { AssertUtil.notNull(password, "Password must not be null"); return withPassword(password.toCharArray()); } /** * Configures authentication. * * @param password the password * @return the builder */ public RedisConnectionConfig.Builder withPassword(char[] password) { AssertUtil.notNull(password, "Password must not be null"); this.password = Arrays.copyOf(password, password.length); return this; } /** * Configures a timeout. * * @param timeout must not be {@literal null} or negative. * @return the builder */ public RedisConnectionConfig.Builder withTimeout(long timeout) { AssertUtil.notNull(timeout, "Timeout must not be null"); AssertUtil.notNull(timeout >= 0, "Timeout must be greater or equal 0"); this.timeout = timeout; return this; } /** * Configures a redisSentinel master Id. * * @param sentinelMasterId redisSentinel master id, must not be empty or {@literal null} * @return the builder */ public RedisConnectionConfig.Builder withSentinelMasterId(String sentinelMasterId) { AssertUtil.notEmpty(sentinelMasterId, "Sentinel master id must not empty"); this.redisSentinelMasterId = sentinelMasterId; return this; } /** * Sets the sslEnable. * * @param sslEnable sslEnable * @return the value of Builder */ public RedisConnectionConfig.Builder withSslEnable(boolean sslEnable) { this.sslEnable = sslEnable; return this; } /** * Sets the trustedCertificatesPath. * * @param trustedCertificatesPath trustedCertificatesPath * @return the value of Builder */ public RedisConnectionConfig.Builder withTrustedCertificatesPath(String trustedCertificatesPath) { AssertUtil.notEmpty(trustedCertificatesPath, "trusted certificates path must not empty"); this.trustedCertificatesPath = trustedCertificatesPath; return this; } /** * Sets the trustedCertificatesJksPassword. * * @param trustedCertificatesJksPassword trustedCertificatesJksPassword * @return the value of Builder */ public RedisConnectionConfig.Builder withTrustedCertificatesJksPassword(String trustedCertificatesJksPassword) { this.trustedCertificatesJksPassword = trustedCertificatesJksPassword; return this; } /** * Sets the keyCertChainFilePath. * * @param keyCertChainFilePath keyCertChainFilePath * @return the value of Builder */ public RedisConnectionConfig.Builder withKeyCertChainFilePath(String keyCertChainFilePath) { this.keyCertChainFilePath = keyCertChainFilePath; return this; } /** * Sets the keyFilePath. * * @param keyFilePath keyFilePath * @return the value of Builder */ public RedisConnectionConfig.Builder withKeyFilePath(String keyFilePath) { this.keyFilePath = keyFilePath; return this; } /** * Sets the keyFilePassword. * * @param keyFilePassword keyFilePassword * @return the value of Builder */ public RedisConnectionConfig.Builder withKeyFilePassword(String keyFilePassword) { this.keyFilePassword = keyFilePassword; return this; } /** * @return the RedisConnectionConfig. */ public RedisConnectionConfig build() { if (redisSentinels.isEmpty() && redisClusters.isEmpty() && StringUtil.isEmpty(host)) { throw new IllegalStateException( "Cannot build a RedisConnectionConfig. One of the following must be provided Host, Socket, Cluster or " + "Sentinel"); } RedisConnectionConfig redisConnectionConfig = new RedisConnectionConfig(); redisConnectionConfig.setHost(host); redisConnectionConfig.setPort(port); if (sslEnable){ redisConnectionConfig.setSslEnable(true); redisConnectionConfig.setTrustedCertificatesPath(trustedCertificatesPath); redisConnectionConfig.setTrustedCertificatesJksPassword(trustedCertificatesJksPassword); redisConnectionConfig.setKeyCertChainFilePath(keyCertChainFilePath); redisConnectionConfig.setKeyFilePath(keyFilePath); redisConnectionConfig.setKeyFilePassword(keyFilePassword); } if (password != null) { redisConnectionConfig.setPassword(password); } redisConnectionConfig.setDatabase(database); redisConnectionConfig.setClientName(clientName); redisConnectionConfig.setRedisSentinelMasterId(redisSentinelMasterId); for (RedisHostAndPort sentinel : redisSentinels) { redisConnectionConfig.getRedisSentinels().add( new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout)); } for (RedisHostAndPort sentinel : redisClusters) { redisConnectionConfig.getRedisClusters().add( new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout)); } redisConnectionConfig.setTimeout(timeout); return redisConnectionConfig; } } /** * Return true for valid port numbers. */ private static boolean isValidPort(int port) { return port >= 0 && port <= 65535; } /** * Gets the value of trustedCertificatesPath. * * @return the value of trustedCertificatesPath */ public String getTrustedCertificatesPath() { return trustedCertificatesPath; } /** * Sets the trustedCertificatesPath. *

          *

          You can use getTrustedCertificatesPath() to get the value of trustedCertificatesPath

          * * @param trustedCertificatesPath trustedCertificatesPath */ public void setTrustedCertificatesPath(String trustedCertificatesPath) { this.trustedCertificatesPath = trustedCertificatesPath; } /** * Gets the value of trustedCertificatesJksPassword. * * @return the value of trustedCertificatesJksPassword */ public String getTrustedCertificatesJksPassword() { return trustedCertificatesJksPassword; } /** * Sets the trustedCertificatesJksPassword. *

          *

          You can use getTrustedCertificatesJksPassword() to get the value of trustedCertificatesJksPassword

          * * @param trustedCertificatesJksPassword trustedCertificatesJksPassword */ public void setTrustedCertificatesJksPassword(String trustedCertificatesJksPassword) { this.trustedCertificatesJksPassword = trustedCertificatesJksPassword; } /** * Gets the value of keyCertChainFilePath. * * @return the value of keyCertChainFilePath */ public String getKeyCertChainFilePath() { return keyCertChainFilePath; } /** * Sets the keyCertChainFilePath. *

          *

          You can use getKeyCertChainFilePath() to get the value of keyCertChainFilePath

          * * @param keyCertChainFilePath keyCertChainFilePath */ public void setKeyCertChainFilePath(String keyCertChainFilePath) { this.keyCertChainFilePath = keyCertChainFilePath; } /** * Gets the value of keyFilePath. * * @return the value of keyFilePath */ public String getKeyFilePath() { return keyFilePath; } /** * Sets the keyFilePath. *

          *

          You can use getKeyFilePath() to get the value of keyFilePath

          * * @param keyFilePath keyFilePath */ public void setKeyFilePath(String keyFilePath) { this.keyFilePath = keyFilePath; } /** * Gets the value of keyFilePassword. * * @return the value of keyFilePassword */ public String getKeyFilePassword() { return keyFilePassword; } /** * Sets the keyFilePassword. *

          *

          You can use getKeyFilePassword() to get the value of keyFilePassword

          * * @param keyFilePassword keyFilePassword */ public void setKeyFilePassword(String keyFilePassword) { this.keyFilePassword = keyFilePassword; } /** * Sets the sslEnable. *

          *

          You can use isSslEnable() to get the value of sslEnable

          * * @param sslEnable sslEnable */ public void setSslEnable(boolean sslEnable) { this.sslEnable = sslEnable; } /** * Gets the value of sslEnable. * * @return the value of sslEnable */ public boolean isSslEnable() { return sslEnable; } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisHostAndPort.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis.config; import com.alibaba.csp.sentinel.util.AssertUtil; /** * An immutable representation of a host and port. * * @author tiger */ public class RedisHostAndPort { private static final int NO_PORT = -1; public final String host; public final int port; /** * @param host must not be empty or {@literal null}. * @param port */ private RedisHostAndPort(String host, int port) { AssertUtil.notNull(host, "host must not be null"); this.host = host; this.port = port; } /** * Create a {@link RedisHostAndPort} of {@code host} and {@code port} * * @param host the hostname * @param port a valid port * @return the {@link RedisHostAndPort} of {@code host} and {@code port} */ public static RedisHostAndPort of(String host, int port) { AssertUtil.isTrue(isValidPort(port), String.format("Port out of range: %s", port)); return new RedisHostAndPort(host, port); } /** * @return {@literal true} if has a port. */ public boolean hasPort() { return port != NO_PORT; } /** * @return the host text. */ public String getHost() { return host; } /** * @return the port. */ public int getPort() { if (!hasPort()) { throw new IllegalStateException("No port present."); } return port; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof RedisHostAndPort)) { return false; } RedisHostAndPort that = (RedisHostAndPort)o; return port == that.port && (host != null ? host.equals(that.host) : that.host == null); } @Override public int hashCode() { int result = host != null ? host.hashCode() : 0; result = 31 * result + port; return result; } /** * @param port the port number * @return {@literal true} for valid port numbers. */ private static boolean isValidPort(int port) { return port >= 0 && port <= 65535; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(host); if (hasPort()) { sb.append(':').append(port); } return sb.toString(); } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/ClusterModeRedisDataSourceTest.java ================================================ /* * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import io.lettuce.core.RedisURI; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.SlotHash; import io.lettuce.core.cluster.api.sync.NodeSelection; import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; import org.hamcrest.Matchers; import org.junit.*; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; /** * Redis redisCluster mode test cases for {@link RedisDataSource}. * * @author liqiangz */ @Ignore(value = "Before run this test, you need to set up your Redis Cluster.") public class ClusterModeRedisDataSourceTest { private String host = "localhost"; private int redisSentinelPort = 7000; private final RedisClusterClient client = RedisClusterClient.create(RedisURI.Builder.redis(host, redisSentinelPort).build()); private String ruleKey = "sentinel.rules.flow.ruleKey"; private String channel = "sentinel.rules.flow.channel"; @Before public void initData() { Converter> flowConfigParser = buildFlowConfigParser(); RedisConnectionConfig config = RedisConnectionConfig.builder() .withRedisCluster(host, redisSentinelPort).build(); initRedisRuleData(); ReadableDataSource> redisDataSource = new RedisDataSource<>(config, ruleKey, channel, flowConfigParser); FlowRuleManager.register2Property(redisDataSource.getProperty()); } @Test public void testConnectToSentinelAndPubMsgSuccess() { int maxQueueingTimeMs = new Random().nextInt(); String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs + ", \"controller\":null}]"; RedisAdvancedClusterCommands subCommands = client.connect().sync(); int slot = SlotHash.getSlot(ruleKey); NodeSelection nodes = subCommands.nodes((n) -> n.hasSlot(slot)); RedisCommands commands = nodes.commands(0); commands.multi(); commands.set(ruleKey, flowRulesJson); commands.publish(channel, flowRulesJson); commands.exec(); await().timeout(2, TimeUnit.SECONDS) .until(new Callable>() { @Override public List call() throws Exception { return FlowRuleManager.getRules(); } }, Matchers.hasSize(1)); List rules = FlowRuleManager.getRules(); Assert.assertEquals(rules.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); String value = subCommands.get(ruleKey); List flowRulesValuesInRedis = buildFlowConfigParser().convert(value); Assert.assertEquals(flowRulesValuesInRedis.size(), 1); Assert.assertEquals(flowRulesValuesInRedis.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); } @After public void clearResource() { RedisAdvancedClusterCommands stringRedisCommands = client.connect().sync(); stringRedisCommands.del(ruleKey); client.shutdown(); } private Converter> buildFlowConfigParser() { return source -> JSON.parseObject(source, new TypeReference>() { }); } private void initRedisRuleData() { String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; RedisAdvancedClusterCommands stringRedisCommands = client.connect().sync(); String ok = stringRedisCommands.set(ruleKey, flowRulesJson); Assert.assertEquals("OK", ok); } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/RedisConnectionConfigTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import org.junit.Assert; import org.junit.Test; /** * Test cases for {@link RedisConnectionConfig}. * * @author tiger */ public class RedisConnectionConfigTest { @Test public void testRedisDefaultPropertySuccess() { String host = "localhost"; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host).build(); Assert.assertEquals(host, redisConnectionConfig.getHost()); Assert.assertEquals(RedisConnectionConfig.DEFAULT_REDIS_PORT, redisConnectionConfig.getPort()); Assert.assertEquals(RedisConnectionConfig.DEFAULT_TIMEOUT_MILLISECONDS, redisConnectionConfig.getTimeout()); Assert.assertFalse(redisConnectionConfig.isSslEnable()); } @Test public void testRedisClientNamePropertySuccess() { String host = "localhost"; String clientName = "clientName"; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) .withClientName("clientName") .build(); Assert.assertEquals(redisConnectionConfig.getClientName(), clientName); } @Test public void testRedisTimeOutPropertySuccess() { String host = "localhost"; long timeout = 70 * 1000; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) .withTimeout(timeout) .build(); Assert.assertEquals(redisConnectionConfig.getTimeout(), timeout); } @Test public void testRedisSentinelDefaultPortSuccess() { String host = "localhost"; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) .withPassword("211233") .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(1, redisConnectionConfig.getRedisSentinels().size()); Assert.assertEquals(RedisConnectionConfig.DEFAULT_SENTINEL_PORT, redisConnectionConfig.getRedisSentinels().get(0).getPort()); } @Test public void testRedisSentinelMoreThanOneServerSuccess() { String host = "localhost"; String host2 = "server2"; int port2 = 1879; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) .withRedisSentinel(host2, port2) .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(2, redisConnectionConfig.getRedisSentinels().size()); } @Test public void testRedisSentinelMoreThanOneDuplicateServerSuccess() { String host = "localhost"; String host2 = "server2"; int port2 = 1879; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisSentinel(host) .withRedisSentinel(host2, port2) .withRedisSentinel(host2, port2) .withPassword("211233") .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(3, redisConnectionConfig.getRedisSentinels().size()); } @Test public void testRedisClusterDefaultPortSuccess() { String host = "localhost"; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisCluster(host) .withPassword("211233") .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(1, redisConnectionConfig.getRedisClusters().size()); Assert.assertEquals(RedisConnectionConfig.DEFAULT_CLUSTER_PORT, redisConnectionConfig.getRedisClusters().get(0).getPort()); } @Test public void testRedisClusterMoreThanOneServerSuccess() { String host = "localhost"; String host2 = "server2"; int port1 = 1879; int port2 = 1880; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisCluster(host, port1) .withRedisCluster(host2, port2) .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(2, redisConnectionConfig.getRedisClusters().size()); } @Test public void testRedisClusterMoreThanOneDuplicateServerSuccess() { String host = "localhost"; String host2 = "server2"; int port2 = 1879; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redisCluster(host) .withRedisCluster(host2, port2) .withRedisCluster(host2, port2) .withPassword("211233") .build(); Assert.assertNull(redisConnectionConfig.getHost()); Assert.assertEquals(3, redisConnectionConfig.getRedisClusters().size()); } @Test public void testRedisSsl() throws Exception { String host = "localhost"; int port = 1879; String trustedCertificatesPath = "trustedCertificatesPath"; String trustedCertificatesJksPassword = "trustedCertificatesJksPassword"; String keyCertChainFilePath = "keyCertChainFilePath"; String keyFilePath = "keyFilePath"; String keyFilePassword = "keyFilePassword"; RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host) .withHost(host) .withPort(port) .withSslEnable(true) .withTrustedCertificatesPath(trustedCertificatesPath) .withTrustedCertificatesJksPassword(trustedCertificatesJksPassword) .withKeyCertChainFilePath(keyCertChainFilePath) .withKeyFilePath(keyFilePath) .withKeyFilePassword(keyFilePassword) .build(); Assert.assertTrue(redisConnectionConfig.isSslEnable()); Assert.assertEquals(redisConnectionConfig.getTrustedCertificatesPath(), trustedCertificatesPath); Assert.assertEquals(redisConnectionConfig.getTrustedCertificatesJksPassword(), trustedCertificatesJksPassword); Assert.assertEquals(redisConnectionConfig.getKeyCertChainFilePath(), keyCertChainFilePath); Assert.assertEquals(redisConnectionConfig.getKeyFilePath(), keyFilePath); Assert.assertEquals(redisConnectionConfig.getKeyFilePassword(), keyFilePassword); } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/SentinelModeRedisDataSourceTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.sync.RedisCommands; import org.hamcrest.Matchers; import org.junit.*; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; /** * Redis redisSentinel mode test cases for {@link RedisDataSource}. * * @author tiger */ @Ignore(value = "Before run this test, you need to set up your Redis Sentinel.") public class SentinelModeRedisDataSourceTest { private String host = "localhost"; private int redisSentinelPort = 5000; private String redisSentinelMasterId = "myMaster"; private String ruleKey = "sentinel.rules.flow.ruleKey"; private String channel = "sentinel.rules.flow.channel"; private final RedisClient client = RedisClient.create(RedisURI.Builder.sentinel(host, redisSentinelPort) .withSentinelMasterId(redisSentinelMasterId).build()); @Before public void initData() { Converter> flowConfigParser = buildFlowConfigParser(); RedisConnectionConfig config = RedisConnectionConfig.builder() .withRedisSentinel(host, redisSentinelPort) .withRedisSentinel(host, redisSentinelPort) .withSentinelMasterId(redisSentinelMasterId).build(); initRedisRuleData(); ReadableDataSource> redisDataSource = new RedisDataSource<>(config, ruleKey, channel, flowConfigParser); FlowRuleManager.register2Property(redisDataSource.getProperty()); } @Test public void testConnectToSentinelAndPubMsgSuccess() { int maxQueueingTimeMs = new Random().nextInt(); String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs + ", \"controller\":null}]"; RedisCommands subCommands = client.connect().sync(); subCommands.multi(); subCommands.set(ruleKey, flowRulesJson); subCommands.publish(channel, flowRulesJson); subCommands.exec(); await().timeout(2, TimeUnit.SECONDS) .until(new Callable>() { @Override public List call() throws Exception { return FlowRuleManager.getRules(); } }, Matchers.hasSize(1)); List rules = FlowRuleManager.getRules(); Assert.assertEquals(rules.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); String value = subCommands.get(ruleKey); List flowRulesValuesInRedis = buildFlowConfigParser().convert(value); Assert.assertEquals(flowRulesValuesInRedis.size(), 1); Assert.assertEquals(flowRulesValuesInRedis.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); } @After public void clearResource() { RedisCommands stringRedisCommands = client.connect().sync(); stringRedisCommands.del(ruleKey); client.shutdown(); } private Converter> buildFlowConfigParser() { return source -> JSON.parseObject(source, new TypeReference>() {}); } private void initRedisRuleData() { String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; RedisCommands stringRedisCommands = client.connect().sync(); String ok = stringRedisCommands.set(ruleKey, flowRulesJson); Assert.assertEquals("OK", ok); } } ================================================ FILE: sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/StandaloneRedisDataSourceTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.redis; import ai.grakn.redismock.RedisServer; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; /** * Redis stand-alone mode test cases for {@link RedisDataSource}. * * @author tiger */ public class StandaloneRedisDataSourceTest { private static RedisServer server = null; private RedisClient client; private String ruleKey = "sentinel.rules.flow.ruleKey"; private String channel = "sentinel.rules.flow.channel"; @Before public void buildResource() { try { // Bind to a random port. server = RedisServer.newRedisServer(); server.start(); } catch (IOException e) { e.printStackTrace(); } Converter> flowConfigParser = buildFlowConfigParser(); client = RedisClient.create(RedisURI.create(server.getHost(), server.getBindPort())); RedisConnectionConfig config = RedisConnectionConfig.builder() .withHost(server.getHost()) .withPort(server.getBindPort()) .build(); initRedisRuleData(); ReadableDataSource> redisDataSource = new RedisDataSource>(config, ruleKey, channel, flowConfigParser); FlowRuleManager.register2Property(redisDataSource.getProperty()); } @Test public void testPubMsgAndReceiveSuccess() { List rules = FlowRuleManager.getRules(); Assert.assertEquals(1, rules.size()); int maxQueueingTimeMs = new Random().nextInt(); StatefulRedisPubSubConnection connection = client.connectPubSub(); String flowRules = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":" + maxQueueingTimeMs + ", \"controller\":null}]"; RedisPubSubCommands subCommands = connection.sync(); subCommands.multi(); subCommands.set(ruleKey, flowRules); subCommands.publish(channel, flowRules); subCommands.exec(); await().timeout(2, TimeUnit.SECONDS) .until(new Callable>() { @Override public List call() throws Exception { return FlowRuleManager.getRules(); } }, Matchers.hasSize(1)); rules = FlowRuleManager.getRules(); Assert.assertEquals(rules.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); String value = subCommands.get(ruleKey); List flowRulesValuesInRedis = buildFlowConfigParser().convert(value); Assert.assertEquals(flowRulesValuesInRedis.size(), 1); Assert.assertEquals(flowRulesValuesInRedis.get(0).getMaxQueueingTimeMs(), maxQueueingTimeMs); } @Test public void testInitAndParseFlowRuleSuccess() { RedisCommands stringRedisCommands = client.connect().sync(); String value = stringRedisCommands.get(ruleKey); List flowRules = buildFlowConfigParser().convert(value); Assert.assertEquals(flowRules.size(), 1); stringRedisCommands.del(ruleKey); } @Test public void testReadResourceFail() { RedisCommands stringRedisCommands = client.connect().sync(); stringRedisCommands.del(ruleKey); String value = stringRedisCommands.get(ruleKey); Assert.assertNull(value); } @After public void clearResource() { RedisCommands stringRedisCommands = client.connect().sync(); stringRedisCommands.del(ruleKey); client.shutdown(); server.stop(); server = null; } private Converter> buildFlowConfigParser() { return source -> JSON.parseObject(source, new TypeReference>() {}); } private void initRedisRuleData() { String flowRulesJson = "[{\"resource\":\"test\", \"limitApp\":\"default\", \"grade\":1, \"count\":\"0.0\", \"strategy\":0, " + "\"refResource\":null, " + "\"controlBehavior\":0, \"warmUpPeriodSec\":10, \"maxQueueingTimeMs\":500, \"controller\":null}]"; RedisCommands stringRedisCommands = client.connect().sync(); String ok = stringRedisCommands.set(ruleKey, flowRulesJson); Assert.assertEquals(ok, "OK"); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/README.md ================================================ # Sentinel DataSource Spring Cloud Config Sentinel DataSource Spring Cloud Config provides integration with Spring Cloud Config so that Spring Cloud Config can be the dynamic rule data source of Sentinel. To use Sentinel DataSource Spring Cloud Config, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-spring-cloud-config x.y.z ``` Then you can create an `SpringCloudConfigDataSource` and register to rule managers. For instance: ```Java ReadableDataSource> flowRuleDs = new SpringCloudConfigDataSource<>(ruleKey, s -> JSON.parseArray(s, FlowRule.class)); FlowRuleManager.register2Property(flowRuleDs.getProperty()); ``` To notify the client that the remote config has changed, we could bind a git webhook callback with the `com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator.refresh` API. We may refer to the the sample `com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest#refresh` in test cases. We offer test cases and demo in the package: `com.alibaba.csp.sentinel.datasource.spring.cloud.config.test`. When you are running test cases, please follow the steps: ```plaintext // First, start the Spring Cloud config server com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer // Second, start the Spring Cloud config client com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient // Third, run the test cases and demo com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SentinelRuleLocatorTests com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest ``` ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-spring-cloud-config jar 2.1.3.RELEASE 2.1.9.RELEASE com.alibaba.csp sentinel-datasource-extension org.springframework.cloud spring-cloud-starter-config ${spring.cloud.version} org.springframework.retry spring-retry 1.2.4.RELEASE org.springframework spring-core org.springframework.boot spring-boot-starter-test ${spring.boot.version} test org.springframework.cloud spring-cloud-config-server ${spring.cloud.version} test org.springframework.boot spring-boot-starter-web ${spring.boot.version} test org.springframework spring-expression 5.2.24.RELEASE test com.alibaba fastjson test junit junit test ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config; import com.alibaba.csp.sentinel.log.RecordLog; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.cloud.config.client.ConfigClientProperties; import org.springframework.cloud.config.client.ConfigClientStateHolder; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.core.annotation.Order; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.retry.annotation.Retryable; import org.springframework.util.Base64Utils; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.springframework.cloud.config.client.ConfigClientProperties.*; /** *

          * {@link SentinelRuleLocator} which pulls Sentinel rules from remote server. * It retrieves configurations of spring-cloud-config client configurations from * {@link org.springframework.core.env.Environment}, such as {@code spring.cloud.config.uri=uri}, * {@code spring.cloud.config.profile=profile}, and so on. * When rules are pulled successfully, it will be stored to {@link SentinelRuleStorage}. *

          * * @author lianglin * @since 1.7.0 */ @Order(0) public class SentinelRuleLocator implements PropertySourceLocator { private RestTemplate restTemplate; private ConfigClientProperties defaultProperties; private org.springframework.core.env.Environment environment; public SentinelRuleLocator(ConfigClientProperties defaultProperties, org.springframework.core.env.Environment environment) { this.defaultProperties = defaultProperties; this.environment = environment; } /** * Responsible for pull data from remote server * * @param environment * @return correct data if success else a empty propertySource or null */ @Override @Retryable(interceptor = "configServerRetryInterceptor") public org.springframework.core.env.PropertySource locate( org.springframework.core.env.Environment environment) { ConfigClientProperties properties = this.defaultProperties.override(environment); CompositePropertySource composite = new CompositePropertySource("configService"); RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties) : this.restTemplate; Exception error = null; String errorBody = null; try { String[] labels = new String[] {""}; if (StringUtils.hasText(properties.getLabel())) { labels = StringUtils .commaDelimitedListToStringArray(properties.getLabel()); } String state = ConfigClientStateHolder.getState(); // Try all the labels until one works for (String label : labels) { Environment result = getRemoteEnvironment(restTemplate, properties, label.trim(), state); if (result != null) { log(result); // result.getPropertySources() can be null if using xml if (result.getPropertySources() != null) { for (PropertySource source : result.getPropertySources()) { @SuppressWarnings("unchecked") Map map = (Map)source .getSource(); composite.addPropertySource( new MapPropertySource(source.getName(), map)); } } SentinelRuleStorage.setRulesSource(composite); return composite; } } } catch (HttpServerErrorException e) { error = e; if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders().getContentType())) { errorBody = e.getResponseBodyAsString(); } } catch (Exception e) { error = e; } if (properties.isFailFast()) { throw new IllegalStateException( "Could not locate PropertySource and the fail fast property is set, failing", error); } RecordLog.warn("Could not locate PropertySource: " + (errorBody == null ? error == null ? "label not found" : error.getMessage() : errorBody)); return null; } public org.springframework.core.env.PropertySource refresh() { return locate(environment); } private void log(Environment result) { RecordLog.info("Located environment: name={}, profiles={}, label={}, version={}, state={}", result.getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result.getLabel(), result.getVersion(), result.getState()); List propertySourceList = result.getPropertySources(); if (propertySourceList != null) { int propertyCount = 0; for (PropertySource propertySource : propertySourceList) { propertyCount += propertySource.getSource().size(); } RecordLog.info("[SentinelRuleLocator] Environment {} has {} property sources with {} properties", result.getName(), result.getPropertySources().size(), propertyCount); } } private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) { String path = "/{name}/{profile}"; String name = properties.getName(); String profile = properties.getProfile(); String token = properties.getToken(); int noOfUrls = properties.getUri().length; if (noOfUrls > 1) { RecordLog.debug("[SentinelRuleLocator] Multiple Config Server Urls found listed."); } RecordLog.info("[SentinelRuleLocator] getRemoteEnvironment, properties={}, label={}, state={}", properties, label, state); Object[] args = new String[] {name, profile}; if (StringUtils.hasText(label)) { if (label.contains("/")) { label = label.replace("/", "(_)"); } args = new String[] {name, profile, label}; path = path + "/{label}"; } ResponseEntity response = null; for (int i = 0; i < noOfUrls; i++) { Credentials credentials = properties.getCredentials(i); String uri = credentials.getUri(); String username = credentials.getUsername(); String password = credentials.getPassword(); RecordLog.info("[SentinelRuleLocator] Fetching config from server at: {}", uri); try { HttpHeaders headers = new HttpHeaders(); addAuthorizationToken(properties, headers, username, password); if (StringUtils.hasText(token)) { headers.add(TOKEN_HEADER, token); } if (StringUtils.hasText(state) && properties.isSendState()) { headers.add(STATE_HEADER, state); } final HttpEntity entity = new HttpEntity<>((Void)null, headers); response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args); } catch (HttpClientErrorException e) { if (e.getStatusCode() != HttpStatus.NOT_FOUND) { throw e; } } catch (ResourceAccessException e) { RecordLog.warn("[SentinelRuleLocator] ConnectTimeoutException on url <{}>." + " Will be trying the next url if available", uri); if (i == noOfUrls - 1) { throw e; } else { continue; } } if (response == null || response.getStatusCode() != HttpStatus.OK) { return null; } Environment result = response.getBody(); return result; } return null; } private RestTemplate getSecureRestTemplate(ConfigClientProperties client) { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); if (client.getRequestReadTimeout() < 0) { throw new IllegalStateException("Invalid Value for Read Timeout set."); } requestFactory.setReadTimeout(client.getRequestReadTimeout()); RestTemplate template = new RestTemplate(requestFactory); Map headers = new HashMap<>(client.getHeaders()); if (headers.containsKey(AUTHORIZATION)) { // To avoid redundant addition of header headers.remove(AUTHORIZATION); } if (!headers.isEmpty()) { template.setInterceptors(Arrays.asList( new GenericRequestHeaderInterceptor(headers))); } return template; } private void addAuthorizationToken(ConfigClientProperties configClientProperties, HttpHeaders httpHeaders, String username, String password) { String authorization = configClientProperties.getHeaders().get(AUTHORIZATION); if (password != null && authorization != null) { throw new IllegalStateException( "You must set either 'password' or 'authorization'"); } if (password != null) { byte[] token = Base64Utils.encode((username + ":" + password).getBytes()); httpHeaders.add("Authorization", "Basic " + new String(token)); } else if (authorization != null) { httpHeaders.add("Authorization", authorization); } } public void setRestTemplate(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public static class GenericRequestHeaderInterceptor implements ClientHttpRequestInterceptor { private final Map headers; public GenericRequestHeaderInterceptor(Map headers) { this.headers = headers; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { for (Map.Entry header : headers.entrySet()) { request.getHeaders().add(header.getKey(), header.getValue()); } return execution.execute(request, body); } protected Map getHeaders() { return headers; } } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config; import org.springframework.core.env.PropertySource; /** * Storage data pull from spring-config-cloud server * And notice ${@link SpringCloudConfigDataSource} update latest values * * @author lianglin * @since 1.7.0 */ public class SentinelRuleStorage { public static PropertySource rulesSource; public static void setRulesSource(PropertySource source) { rulesSource = source; noticeSpringCloudDataSource(); } public static String retrieveRule(String ruleKey) { return rulesSource == null ? null : (String) rulesSource.getProperty(ruleKey); } private static void noticeSpringCloudDataSource(){ SpringCloudConfigDataSource.updateValues(); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** *

          A read-only {@code DataSource} with Spring Cloud Config backend.

          *

          * It retrieves the Spring Cloud Config data stored in {@link SentinelRuleStorage}. * When the data in the backend has been modified, {@link SentinelRuleStorage} will * invoke {@link SpringCloudConfigDataSource#updateValues()} to update values dynamically. *

          *

          * To notify the client that the remote config has changed, users could bind a git * webhook callback with the {@link SentinelRuleLocator#refresh()} API. *

          * * @author lianglin * @since 1.7.0 */ public class SpringCloudConfigDataSource extends AbstractDataSource { private final static Map listeners; static { listeners = new ConcurrentHashMap<>(); } private final String ruleKey; public SpringCloudConfigDataSource(final String ruleKey, Converter converter) { super(converter); if (StringUtil.isBlank(ruleKey)) { throw new IllegalArgumentException(String.format("Bad argument: ruleKey=[%s]", ruleKey)); } this.ruleKey = ruleKey; loadInitialConfig(); initListener(); } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn("[SpringCloudConfigDataSource] WARN: initial application is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[SpringCloudConfigDataSource] Error when loading initial application", ex); } } private void initListener() { listeners.put(this, new SpringConfigListener(this)); } @Override public String readSource() { return SentinelRuleStorage.retrieveRule(ruleKey); } @Override public void close() throws Exception { listeners.remove(this); } public static void updateValues() { for (SpringConfigListener listener : listeners.values()) { listener.listenChanged(); } } private static class SpringConfigListener { private SpringCloudConfigDataSource dataSource; public SpringConfigListener(SpringCloudConfigDataSource dataSource) { this.dataSource = dataSource; } public void listenChanged() { try { Object newValue = dataSource.loadConfig(); dataSource.getProperty().updateValue(newValue); } catch (Exception e) { RecordLog.warn("[SpringConfigListener] load config error: ", e); } } } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config.config; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.config.client.ConfigClientProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; /** *

          * Define the configuration Loaded when spring application start. * Put it in META-INF/spring.factories, it will be auto loaded by Spring *

          * * @author lianglin * @since 1.7.0 */ @Configuration public class DataSourceBootstrapConfiguration { @Autowired private ConfigurableEnvironment environment; @Bean public SentinelRuleLocator sentinelPropertySourceLocator(ConfigClientProperties properties) { SentinelRuleLocator locator = new SentinelRuleLocator( properties, environment); return locator; } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories ================================================ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java ================================================ /* * Copyright (C) 2018 the original author or 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. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author lianglin * @since 1.7.0 */ @SpringBootApplication public abstract class SimpleSpringApplication { public static void main(String[] args) { SpringApplication.run(SimpleSpringApplication.class); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; /** * @author lianglin * @since 1.7.0 */ @SpringBootApplication @ComponentScan("com.alibaba.csp.sentinel.datasource.spring.cloud.config.test") @PropertySource("classpath:config-client-application.properties") public class ConfigClient { public static void main(String[] args) { SpringApplication.run(ConfigClient.class); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.context.annotation.PropertySource; /** * @author lianglin * @since 1.7.0 */ @EnableConfigServer @SpringBootApplication @PropertySource("classpath:config-server-application.properties") public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java ================================================ /* * Copyright 2018-2019 the original author or 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 * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config.test; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleStorage; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer; import com.alibaba.csp.sentinel.util.StringUtil; import org.junit.Assert; 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.cloud.config.client.ConfigClientProperties; import org.springframework.core.env.Environment; import org.springframework.test.context.junit4.SpringRunner; /** * @author lianglin * @since 1.7.0 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = DataSourceBootstrapConfiguration.class, properties = { "spring.application.name=sentinel" }) public class SentinelRuleLocatorTests { @Autowired private SentinelRuleLocator sentinelRulesSourceLocator; @Autowired private Environment environment; @Test public void testAutoLoad() { Assert.assertTrue(sentinelRulesSourceLocator != null); Assert.assertTrue(environment != null); } /** * Before run this test case, please start the Config Server ${@link ConfigServer} */ public void testLocate() { ConfigClientProperties configClientProperties = new ConfigClientProperties(environment); configClientProperties.setLabel("master"); configClientProperties.setProfile("dev"); configClientProperties.setUri(new String[]{"http://localhost:10086/"}); SentinelRuleLocator sentinelRulesSourceLocator = new SentinelRuleLocator(configClientProperties, environment); sentinelRulesSourceLocator.locate(environment); Assert.assertTrue(StringUtil.isNotBlank(SentinelRuleStorage.retrieveRule("flow_rule"))); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.datasource.spring.cloud.config.test; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SpringCloudConfigDataSource; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient; import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * Before test, please start ${@link ConfigServer} and ${@link ConfigClient} * * @author lianglin * @since 1.7.0 */ @RestController @RequestMapping(value = "/test/dataSource/") public class SpringCouldDataSourceTest { @Autowired private SentinelRuleLocator locator; Converter> converter = new Converter>() { @Override public List convert(String source) { return JSON.parseArray(source, FlowRule.class); } }; @GetMapping("/get") @ResponseBody public List get() { SpringCloudConfigDataSource dataSource = new SpringCloudConfigDataSource("flow_rule", converter); FlowRuleManager.register2Property(dataSource.getProperty()); return FlowRuleManager.getRules(); } /** * WebHook refresh config */ @GetMapping("/refresh") @ResponseBody public List refresh() { locator.refresh(); return FlowRuleManager.getRules(); } } ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml ================================================ spring: application: name: sentinel cloud: config: uri: http://localhost:10086/ profile: dev label: master ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties ================================================ spring.application.name=sentinel server.port=8080 ================================================ FILE: sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties ================================================ spring.cloud.config.server.git.uri=git@github.com:linlinisme/spring-cloud-config-datasource.git spring.cloud.config.server.git.search-paths=sentinel server.port=10086 spring.cloud.config.label=master ================================================ FILE: sentinel-extension/sentinel-datasource-zookeeper/README.md ================================================ # Sentinel DataSource ZooKeeper Sentinel DataSource ZooKeeper provides integration with ZooKeeper so that ZooKeeper can be the dynamic rule data source of Sentinel. The data source uses push model (listener). To use Sentinel DataSource ZooKeeper, you should add the following dependency: ```xml com.alibaba.csp sentinel-datasource-zookeeper x.y.z ``` Then you can create an `ZookeeperDataSource` and register to rule managers. For instance: ```java // `path` is the data path in ZooKeeper ReadableDataSource> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, path, source -> JSON.parseObject(source, new TypeReference>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); ``` > Note: It's not recommended to add a large amount of rules to a single path (has limitation, also leads to bad performance). We've also provided an example: [sentinel-demo-zookeeper-datasource](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-zookeeper-datasource). ================================================ FILE: sentinel-extension/sentinel-datasource-zookeeper/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-datasource-zookeeper jar 5.1.0 com.alibaba.csp sentinel-datasource-extension org.apache.curator curator-recipes ${curator.version} junit junit test org.awaitility awaitility test org.apache.curator curator-test ${curator.version} test com.alibaba fastjson test ================================================ FILE: sentinel-extension/sentinel-datasource-zookeeper/src/main/java/com/alibaba/csp/sentinel/datasource/zookeeper/ZookeeperDataSource.java ================================================ package com.alibaba.csp.sentinel.datasource.zookeeper; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.curator.framework.AuthInfo; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.CuratorCache; import org.apache.curator.framework.recipes.cache.CuratorCacheListener; import org.apache.curator.retry.ExponentialBackoffRetry; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * A read-only {@code DataSource} with ZooKeeper backend. * * @author guonanjun */ public class ZookeeperDataSource extends AbstractDataSource { private static final int RETRY_TIMES = 3; private static final int SLEEP_TIME = 1000; private static volatile Map zkClientMap = new HashMap<>(); private static final Object lock = new Object(); private final ExecutorService pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(1), new NamedThreadFactory("sentinel-zookeeper-ds-update", true), new ThreadPoolExecutor.DiscardOldestPolicy()); private CuratorCacheListener listener; private final String path; private CuratorFramework zkClient = null; private CuratorCache nodeCache = null; public ZookeeperDataSource(final String serverAddr, final String path, Converter parser) { super(parser); if (StringUtil.isBlank(serverAddr) || StringUtil.isBlank(path)) { throw new IllegalArgumentException(String.format("Bad argument: serverAddr=[%s], path=[%s]", serverAddr, path)); } this.path = path; init(serverAddr, null); } /** * This constructor is Nacos-style. */ public ZookeeperDataSource(final String serverAddr, final String groupId, final String dataId, Converter parser) { super(parser); if (StringUtil.isBlank(serverAddr) || StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) { throw new IllegalArgumentException(String.format("Bad argument: serverAddr=[%s], groupId=[%s], dataId=[%s]", serverAddr, groupId, dataId)); } this.path = getPath(groupId, dataId); init(serverAddr, null); } /** * This constructor adds authentication information. */ public ZookeeperDataSource(final String serverAddr, final List authInfos, final String groupId, final String dataId, Converter parser) { super(parser); if (StringUtil.isBlank(serverAddr) || StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) { throw new IllegalArgumentException(String.format("Bad argument: serverAddr=[%s], authInfos=[%s], groupId=[%s], dataId=[%s]", serverAddr, authInfos, groupId, dataId)); } this.path = getPath(groupId, dataId); init(serverAddr, authInfos); } private void init(final String serverAddr, final List authInfos) { initZookeeperListener(serverAddr, authInfos); loadInitialConfig(); } private void loadInitialConfig() { try { T newValue = loadConfig(); if (newValue == null) { RecordLog.warn("[ZookeeperDataSource] WARN: initial config is null, you may have to check your data source"); } getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[ZookeeperDataSource] Error when loading initial config", ex); } } private void initZookeeperListener(final String serverAddr, final List authInfos) { try { this.listener = CuratorCacheListener.builder().forNodeCache(() -> { try { T newValue = loadConfig(); RecordLog.info("[ZookeeperDataSource] New property value received for ({}, {}): {}", serverAddr, path, newValue); // Update the new value to the property. getProperty().updateValue(newValue); } catch (Exception ex) { RecordLog.warn("[ZookeeperDataSource] loadConfig exception", ex); } }).build(); String zkKey = getZkKey(serverAddr, authInfos); if (zkClientMap.containsKey(zkKey)) { this.zkClient = zkClientMap.get(zkKey); } else { synchronized (lock) { if (!zkClientMap.containsKey(zkKey)) { CuratorFramework zc = null; if (authInfos == null || authInfos.size() == 0) { zc = CuratorFrameworkFactory.newClient(serverAddr, new ExponentialBackoffRetry(SLEEP_TIME, RETRY_TIMES)); } else { zc = CuratorFrameworkFactory.builder(). connectString(serverAddr). retryPolicy(new ExponentialBackoffRetry(SLEEP_TIME, RETRY_TIMES)). authorization(authInfos). build(); } this.zkClient = zc; this.zkClient.start(); Map newZkClientMap = new HashMap<>(zkClientMap.size()); newZkClientMap.putAll(zkClientMap); newZkClientMap.put(zkKey, zc); zkClientMap = newZkClientMap; } else { this.zkClient = zkClientMap.get(zkKey); } } } this.nodeCache = CuratorCache.build(this.zkClient, this.path); this.nodeCache.listenable().addListener(this.listener, this.pool); this.nodeCache.start(); } catch (Exception e) { RecordLog.warn("[ZookeeperDataSource] Error occurred when initializing Zookeeper data source", e); e.printStackTrace(); } } @Override public String readSource() throws Exception { if (this.zkClient == null) { throw new IllegalStateException("Zookeeper has not been initialized or error occurred"); } String configInfo = null; ChildData childData = nodeCache.get(path).orElse(null); if (null != childData && childData.getData() != null) { configInfo = new String(childData.getData()); } return configInfo; } @Override public void close() throws Exception { if (this.nodeCache != null) { this.nodeCache.listenable().removeListener(listener); this.nodeCache.close(); } if (this.zkClient != null) { this.zkClient.close(); } pool.shutdown(); } private String getPath(String groupId, String dataId) { return String.format("/%s/%s", groupId, dataId); } private String getZkKey(final String serverAddr, final List authInfos) { if (authInfos == null || authInfos.size() == 0) { return serverAddr; } StringBuilder builder = new StringBuilder(64); builder.append(serverAddr).append(getAuthInfosKey(authInfos)); return builder.toString(); } private String getAuthInfosKey(List authInfos) { StringBuilder builder = new StringBuilder(32); for (AuthInfo authInfo : authInfos) { if (authInfo == null) { builder.append("{}"); } else { builder.append("{" + "sc=" + authInfo.getScheme() + ",au=" + Arrays.toString(authInfo.getAuth()) + "}"); } } return builder.toString(); } protected CuratorFramework getZkClient() { return this.zkClient; } } ================================================ FILE: sentinel-extension/sentinel-datasource-zookeeper/src/test/java/com/alibaba/csp/sentinel/datasource/zookeeper/ZookeeperDataSourceTest.java ================================================ package com.alibaba.csp.sentinel.datasource.zookeeper; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.curator.framework.AuthInfo; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.curator.test.TestingServer; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; import org.junit.Assert; import org.junit.Test; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Eric Zhao */ public class ZookeeperDataSourceTest { @Test public void testZooKeeperDataSource() throws Exception { TestingServer server = new TestingServer(21812); server.start(); final String remoteAddress = server.getConnectString(); final String path = "/sentinel-zk-ds-demo/flow-HK"; ReadableDataSource> flowRuleDataSource = new ZookeeperDataSource>(remoteAddress, path, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); CuratorFramework zkClient = CuratorFrameworkFactory.newClient(remoteAddress, new ExponentialBackoffRetry(3, 1000)); zkClient.start(); Stat stat = zkClient.checkExists().forPath(path); if (stat == null) { zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, null); } final String resourceName = "HK"; publishThenTestFor(zkClient, path, resourceName, 10); publishThenTestFor(zkClient, path, resourceName, 15); zkClient.close(); server.stop(); } @Test public void testZooKeeperDataSourceAuthorization() throws Exception { TestingServer server = new TestingServer(21812); server.start(); final String remoteAddress = server.getConnectString(); final String groupId = "sentinel-zk-ds-demo"; final String dataId = "flow-HK"; final String path = "/" + groupId + "/" + dataId; final String scheme = "digest"; final String auth = "root:123456"; AuthInfo authInfo = new AuthInfo(scheme, auth.getBytes()); List authInfoList = Collections.singletonList(authInfo); CuratorFramework zkClient = CuratorFrameworkFactory.builder(). connectString(remoteAddress). retryPolicy(new ExponentialBackoffRetry(3, 100)). authorization(authInfoList). build(); zkClient.start(); Stat stat = zkClient.checkExists().forPath(path); if (stat == null) { ACL acl = new ACL(ZooDefs.Perms.ALL, new Id(scheme, DigestAuthenticationProvider.generateDigest(auth))); zkClient.create().creatingParentContainersIfNeeded().withACL(Collections.singletonList(acl)).forPath(path, null); } ReadableDataSource> flowRuleDataSource = new ZookeeperDataSource>(remoteAddress, authInfoList, groupId, dataId, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); final String resourceName = "HK"; publishThenTestFor(zkClient, path, resourceName, 10); publishThenTestFor(zkClient, path, resourceName, 15); zkClient.close(); server.stop(); } private void publishThenTestFor(CuratorFramework zkClient, String path, String resourceName, long count) throws Exception { FlowRule rule = new FlowRule().setResource(resourceName) .setLimitApp("default") .as(FlowRule.class) .setCount(count) .setGrade(RuleConstant.FLOW_GRADE_QPS); String ruleString = JSON.toJSONString(Collections.singletonList(rule)); zkClient.setData().forPath(path, ruleString.getBytes()); await().timeout(5, TimeUnit.SECONDS) .until(new Callable() { @Override public Boolean call() throws Exception { List rules = FlowRuleManager.getRules(); return rules != null && !rules.isEmpty(); } }); List rules = FlowRuleManager.getRules(); boolean exists = false; for (FlowRule r : rules) { if (resourceName.equals(r.getResource())) { exists = true; assertEquals(count, new Double(r.getCount()).longValue()); } } assertTrue(exists); } /** * Test whether different dataSources can share the same zkClient when the connection parameters are the same. * @throws Exception */ @Test public void testZooKeeperDataSourceSameZkClient() throws Exception { TestingServer server = new TestingServer(21813); server.start(); final String remoteAddress = server.getConnectString(); final String flowPath = "/sentinel-zk-ds-demo/flow-HK"; final String degradePath = "/sentinel-zk-ds-demo/degrade-HK"; ZookeeperDataSource> flowRuleZkDataSource = new ZookeeperDataSource<>(remoteAddress, flowPath, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); ZookeeperDataSource> degradeRuleZkDataSource = new ZookeeperDataSource<>(remoteAddress, degradePath, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); Assert.assertTrue(flowRuleZkDataSource.getZkClient() == degradeRuleZkDataSource.getZkClient()); final String groupId = "sentinel-zk-ds-demo"; final String flowDataId = "flow-HK"; final String degradeDataId = "degrade-HK"; final String scheme = "digest"; final String auth = "root:123456"; AuthInfo authInfo = new AuthInfo(scheme, auth.getBytes()); List authInfoList = Collections.singletonList(authInfo); ZookeeperDataSource> flowRuleZkAutoDataSource = new ZookeeperDataSource>(remoteAddress, authInfoList, groupId, flowDataId, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); ZookeeperDataSource> degradeRuleZkAutoDataSource = new ZookeeperDataSource>(remoteAddress, authInfoList, groupId, degradeDataId, new Converter>() { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } }); Assert.assertTrue(flowRuleZkAutoDataSource.getZkClient() == degradeRuleZkAutoDataSource.getZkClient()); server.stop(); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/README.md ================================================ # Sentinel Metric Exporter Sentinel Metric Exporter is a module which provides the Sentinel metric data exporting ability. Now you can integrate it into your Sentinel application, and then get the metric data in JMX. You can also integrate the JMX data into your monitor system easily, like Prometheus. To use Sentinel Metric Exporter, you should add the following dependency: ```xml com.alibaba.csp sentinel-metric-exporter x.y.z ``` And then you can find the MBean info in your tool. ![MBean Info](https://user-images.githubusercontent.com/25661357/150902723-6350a629-a173-47f9-a94b-6563ae55a5ce.png "MBean Info") ================================================ FILE: sentinel-extension/sentinel-metric-exporter/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-metric-exporter jar com.alibaba.csp sentinel-core junit junit test ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/MetricExporterInit.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.metric.collector.MetricCollector; import com.alibaba.csp.sentinel.metric.exporter.MetricExporter; import com.alibaba.csp.sentinel.metric.exporter.jmx.JMXMetricExporter; import java.util.ArrayList; import java.util.List; /** * The{@link MetricExporterInit} work on load Metric exporters. * * @author chenglu * @date 2021-07-01 19:58 * @since 1.8.3 */ public class MetricExporterInit implements InitFunc { /** * the list of metric exporters. */ private static List metricExporters = new ArrayList<>(); /* load metric exporters. */ static { // now we use this simple way to load MetricExporter. metricExporters.add(new JMXMetricExporter()); } @Override public void init() throws Exception { RecordLog.info("[MetricExporterInit] MetricExporter start init."); // start the metric exporters. for (MetricExporter metricExporter : metricExporters) { try { metricExporter.start(); } catch (Exception e) { RecordLog.warn("[MetricExporterInit] MetricExporterInit start the metricExport[{}] failed, will ignore it.", metricExporter.getClass().getName(), e); } } // add shutdown hook. Runtime.getRuntime().addShutdownHook(new Thread( () -> metricExporters.forEach(metricExporter -> { try { metricExporter.shutdown(); } catch (Exception e) { RecordLog.warn("[MetricExporterInit] MetricExporterInit shutdown the metricExport[{}] failed, will ignore it.", metricExporter.getClass().getName(), e); } }) )); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/collector/MetricCollector.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.collector; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.TimeUtil; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The {@link MetricCollector} work on collecting metrics in {@link MetricNode}. * * @author chenglu * @date 2021-07-01 20:01 * @since 1.8.3 */ public class MetricCollector { /** * collect the metrics in {@link MetricNode}. * * @return the metric grouped by resource name. */ public Map collectMetric() { final long currentTime = TimeUtil.currentTimeMillis(); final long maxTime = currentTime - currentTime % 1000; final long minTime = maxTime - 1000; Map metricNodeMap = new HashMap<>(); for (Map.Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { ClusterNode node = e.getValue(); List metrics = getLastMetrics(node, minTime, maxTime); aggregate(metricNodeMap, metrics, node); } aggregate(metricNodeMap, getLastMetrics(Constants.ENTRY_NODE, minTime, maxTime), Constants.ENTRY_NODE); return metricNodeMap; } /** * Get the last second {@link MetricNode} of {@link ClusterNode} * @param node {@link ClusterNode} * @param minTime the min time. * @param maxTime the max time. * @return the list of {@link MetricNode} */ private List getLastMetrics(ClusterNode node, long minTime, long maxTime) { return node.rawMetricsInMin(time -> time >= minTime && time < maxTime); } /** * aggregate the metrics, the metrics under the same resource will left the lasted value * @param metricNodeMap metrics map * @param metrics metrics info group by timestamp * @param node the node */ private void aggregate(Map metricNodeMap, List metrics, ClusterNode node) { if (metrics == null || metrics.size() == 0) { return; } for (MetricNode metricNode : metrics) { String resource = node.getName(); metricNode.setResource(resource); metricNode.setClassification(node.getResourceType()); MetricNode existMetricNode = metricNodeMap.get(resource); // always keep the MetricNode is the last if (existMetricNode != null && existMetricNode.getTimestamp() > metricNode.getTimestamp()) { continue; } metricNodeMap.put(resource, metricNode); } } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/MetricExporter.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter; /** * {@link MetricExporter} work on export metric to target monitor. * you can implement your export ways by this class. * * @author chenglu * @date 2021-07-01 21:16 */ public interface MetricExporter { /** * start the {@link MetricExporter}. * * @throws Exception start exception. */ void start() throws Exception; /** * export the data to target monitor by the implement. * * @throws Exception export exception. */ void export() throws Exception; /** * shutdown the {@link MetricExporter}. * * @throws Exception shutdown exception. */ void shutdown() throws Exception; } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/JMXMetricExporter.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter.jmx; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.metric.collector.MetricCollector; import com.alibaba.csp.sentinel.metric.exporter.MetricExporter; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * The JMX metric exporter, mainly for write metric datas to JMX bean. It implement {@link MetricExporter}, provide method * start, export and shutdown. The mainly design for the jmx is refresh the JMX bean data scheduled. * {@link JMXExportTask} work on export data to {@link MetricBean}. * * @author chenglu * @date 2021-07-01 20:02 * @since 1.8.3 */ public class JMXMetricExporter implements MetricExporter { /** * schedule executor. */ private final ScheduledExecutorService jmxExporterSchedule; /** * JMX metric writer, write metric datas to {@link MetricBean}. */ private final MetricBeanWriter metricBeanWriter = new MetricBeanWriter(); /** * global metrics collector. */ private final MetricCollector metricCollector = new MetricCollector(); public JMXMetricExporter() { jmxExporterSchedule = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("sentinel-metrics-jmx-exporter-task", true)); } @Override public void start() throws Exception { jmxExporterSchedule.scheduleAtFixedRate(new JMXExportTask(), 1, 1, TimeUnit.SECONDS); } @Override public void export() throws Exception { metricBeanWriter.write(metricCollector.collectMetric()); } @Override public void shutdown() throws Exception { jmxExporterSchedule.shutdown(); } /** * JMXExportTask mainly work on execute the JMX metric export. */ class JMXExportTask implements Runnable { @Override public void run() { try { export(); } catch (Exception e) { RecordLog.warn("[JMX Metric Exporter] export to JMX MetricBean failed.", e); } } } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MBeanRegistry.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter.jmx; import com.alibaba.csp.sentinel.log.RecordLog; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.ObjectName; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * This class provides a unified interface for registering/unregistering of Metric MBean. * * @author chenglu * @date 2021-07-01 20:02 * @since 1.8.3 */ public class MBeanRegistry { private static volatile MBeanRegistry instance = new MBeanRegistry(); private Map mapBean2Name= new ConcurrentHashMap<>(8); private Map mapName2Bean = new ConcurrentHashMap<>(8); private MBeanServer mBeanServer; public static MBeanRegistry getInstance() { return instance; } public MBeanRegistry() { try { mBeanServer = ManagementFactory.getPlatformMBeanServer(); } catch (Error e) { // Account for running within IKVM and create a new MBeanServer // if the PlatformMBeanServer does not exist. mBeanServer = MBeanServerFactory.createMBeanServer(); } } /** * Registers a new MBean with the platform MBean server. * @param bean the bean being registered * @param mBeanName the mBeanName * @throws JMException MBean can not register exception */ public void register(MetricBean bean, String mBeanName) throws JMException { assert bean != null; try { ObjectName oname = new ObjectName(mBeanName); mBeanServer.registerMBean(bean, oname); mapBean2Name.put(bean, mBeanName); mapName2Bean.put(mBeanName, bean); } catch (JMException e) { RecordLog.warn("[MBeanRegistry] Failed to register MBean " + mBeanName, e); throw e; } } /** * unregister the MetricBean * @param bean MetricBean */ public void unRegister(MetricBean bean) { assert bean != null; String beanName = mapBean2Name.get(bean); if (beanName == null) { return; } try { ObjectName objectName = new ObjectName(beanName); mBeanServer.unregisterMBean(objectName); mapBean2Name.remove(bean); mapName2Bean.remove(beanName); } catch (JMException e) { RecordLog.warn("[MBeanRegistry] UnRegister the MetricBean fail", e); } } /** * find the MBean by BeanName * @param mBeanName mBeanName * @return MetricMBean */ public MetricBean findMBean(String mBeanName) { if (mBeanName == null) { return null; } return mapName2Bean.get(mBeanName); } /** * list all MBeans which is registered into MBeanRegistry * @return MetricBeans */ public List listAllMBeans() { return new ArrayList<>(mapName2Bean.values()); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBean.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter.jmx; import com.alibaba.csp.sentinel.node.metric.MetricNode; /** * the MetricBean for JMX expose. * * @author chenglu * @date 2021-07-01 20:02 * @since 1.8.3 */ public class MetricBean implements MetricMXBean { private String resource; /** * Resource classification (e.g. SQL or RPC) */ private int classification; private long timestamp; private long passQps; private long blockQps; private long successQps; private long exceptionQps; private long rt; private long occupiedPassQps; private int concurrency; private long version; @Override public String getResource() { return resource; } @Override public int getClassification() { return classification; } @Override public long getTimestamp() { return timestamp; } @Override public long getPassQps() { return passQps; } @Override public long getBlockQps() { return blockQps; } @Override public long getSuccessQps() { return successQps; } @Override public long getExceptionQps() { return exceptionQps; } @Override public long getRt() { return rt; } @Override public long getOccupiedPassQps() { return occupiedPassQps; } @Override public int getConcurrency() { return concurrency; } @Override public long getVersion() { return version; } /** * set the version to current Mbean. * * @param version current version. */ public void setVersion(long version) { this.version = version; } /** * reset the MBean value to the initialized value. */ public void reset() { this.blockQps = 0; this.passQps = 0; this.timestamp = System.currentTimeMillis(); this.exceptionQps = 0; this.occupiedPassQps = 0; this.successQps = 0; this.rt = 0; this.concurrency = 0; } /** * set the MetricBean's value which from MetricNode. * * @param metricNode metric Node for write file */ public void setValueFromNode(MetricNode metricNode) { if (metricNode == null) { return; } this.successQps = metricNode.getSuccessQps(); this.blockQps = metricNode.getBlockQps(); this.passQps = metricNode.getPassQps(); this.occupiedPassQps = metricNode.getOccupiedPassQps(); this.exceptionQps = metricNode.getExceptionQps(); this.timestamp = metricNode.getTimestamp(); this.classification = metricNode.getClassification(); this.concurrency = metricNode.getConcurrency(); this.resource = metricNode.getResource(); this.rt = metricNode.getRt(); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBeanWriter.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter.jmx; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.util.StringUtil; import javax.management.ObjectName; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; /** * the metric bean writer, it provides {@link MetricBeanWriter#write} method for register the * MetricBean in {@link MBeanRegistry} or update the value of MetricBean * * @author chenglu * @date 2021-07-01 20:02 * @since 1.8.3 */ public class MetricBeanWriter { private final MBeanRegistry mBeanRegistry = MBeanRegistry.getInstance(); private static final String DEFAULT_APP_NAME = "sentinel-application"; private static final Pattern SPECIAL_CHARACTER_PATTERN = Pattern.compile("[*?=:\"\n]"); /** * write the MetricNode value to MetricBean * if the MetricBean is not registered into {@link MBeanRegistry}, * it will be created and registered into {@link MBeanRegistry}. * else it will update the value of MetricBean. * Notes. if the MetricNode is null, then {@link MetricBean} will be reset. * @param map metricNode value group by resource * @throws Exception write failed exception */ public synchronized void write(Map map) throws Exception { if (map == null || map.isEmpty()) { List metricNodes = mBeanRegistry.listAllMBeans(); if (metricNodes == null || metricNodes.isEmpty()) { return; } for (MetricBean metricNode : metricNodes) { metricNode.reset(); } return; } String appName = SentinelConfig.getAppName(); if (appName == null) { appName = DEFAULT_APP_NAME; } long version = System.currentTimeMillis(); // set or update the new metric value for (MetricNode metricNode : map.values()) { // Fix JMX Metrics export error: https://github.com/alibaba/Sentinel/issues/2989 // Without escape, it will throw "cannot add mbean for pattern name" or "invalid character in value part of property" exception String resourceName = escapeSpecialCharacter(metricNode.getResource()); final String mBeanName = "Sentinel:type=Metric,resource=" + resourceName +",classification=" + metricNode.getClassification() +",appName=" + appName; MetricBean metricBean = mBeanRegistry.findMBean(mBeanName); if (metricBean != null) { metricBean.setValueFromNode(metricNode); metricBean.setVersion(version); } else { metricBean = new MetricBean(); metricBean.setValueFromNode(metricNode); metricBean.setVersion(version); mBeanRegistry.register(metricBean, mBeanName); RecordLog.info("[MetricBeanWriter] Registering with JMX as Metric MBean [{}]", mBeanName); } } // reset the old value List metricBeans = mBeanRegistry.listAllMBeans(); if (metricBeans == null || metricBeans.isEmpty()) { return; } for (MetricBean metricBean : metricBeans) { if (!Objects.equals(metricBean.getVersion(), version)) { metricBean.reset(); mBeanRegistry.unRegister(metricBean); } } } /** * escape only when arg has special character eg.(*,?,\n,\") * * @param resourceName need escape resource name * @return escaped characters */ public static String escapeSpecialCharacter(String resourceName) { if (StringUtil.isBlank(resourceName) || !SPECIAL_CHARACTER_PATTERN.matcher(resourceName).find()) { return resourceName; } return ObjectName.quote(resourceName); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricMXBean.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.csp.sentinel.metric.exporter.jmx; /** * the Metric JMX Bean interface. * * @author chenglu * @date 2021-07-01 20:02 * @since 1.8.3 */ public interface MetricMXBean { long getTimestamp(); long getOccupiedPassQps(); long getSuccessQps(); long getPassQps(); long getExceptionQps(); long getBlockQps(); long getRt(); String getResource(); int getClassification(); int getConcurrency(); long getVersion(); } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.metric.MetricExporterInit ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/test/java/com/alibaba/cps/sentinel/metric/exporter/MBeanRegistryTest.java ================================================ package com.alibaba.cps.sentinel.metric.exporter; import com.alibaba.csp.sentinel.metric.exporter.jmx.MBeanRegistry; import com.alibaba.csp.sentinel.metric.exporter.jmx.MetricBean; import org.junit.Assert; import org.junit.Test; import javax.management.JMException; /** * {@link com.alibaba.csp.sentinel.metric.exporter.jmx.MBeanRegistry} unit test. * * @author chenglu * @date 2021-07-01 23:07 */ public class MBeanRegistryTest { @Test public void testMBeanRegistry() throws JMException { MBeanRegistry mBeanRegistry = MBeanRegistry.getInstance(); MetricBean metricBean = new MetricBean(); String mBeanName = "Sentinel:type=test,name=test"; mBeanRegistry.register(metricBean, mBeanName); MetricBean mb1 = mBeanRegistry.findMBean(mBeanName); Assert.assertEquals(mb1, metricBean); mBeanRegistry.unRegister(metricBean); MetricBean mb2 = mBeanRegistry.findMBean(mBeanName); Assert.assertNull(mb2); } } ================================================ FILE: sentinel-extension/sentinel-metric-exporter/src/test/java/com/alibaba/cps/sentinel/metric/exporter/MetricBeanWriterTest.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.alibaba.cps.sentinel.metric.exporter; import com.alibaba.csp.sentinel.metric.exporter.jmx.MetricBeanWriter; import org.junit.Assert; import org.junit.Test; /** * {@link com.alibaba.csp.sentinel.metric.exporter.jmx.MetricBeanWriter} unit test * * @author quguai * @date 2022/12/7 21:33 */ public class MetricBeanWriterTest { @Test public void testEscapeSpecialCharacter() { String character = MetricBeanWriter.escapeSpecialCharacter(null); Assert.assertNull(character); character = MetricBeanWriter.escapeSpecialCharacter(""); Assert.assertEquals("", character); character = MetricBeanWriter.escapeSpecialCharacter("sentinel"); Assert.assertEquals("sentinel", character); character = MetricBeanWriter.escapeSpecialCharacter("*sentinel"); Assert.assertEquals("\"\\*sentinel\"", character); character = MetricBeanWriter.escapeSpecialCharacter("?sentinel"); Assert.assertEquals("\"\\?sentinel\"", character); character = MetricBeanWriter.escapeSpecialCharacter("\nsentinel"); Assert.assertEquals("\"\\nsentinel\"", character); character = MetricBeanWriter.escapeSpecialCharacter("\"sentinel"); Assert.assertEquals("\"\\\"sentinel\"", character); character = MetricBeanWriter.escapeSpecialCharacter("=sentinel"); Assert.assertEquals("\"=sentinel\"", character); character = MetricBeanWriter.escapeSpecialCharacter(":sentinel"); Assert.assertEquals("\":sentinel\"", character); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/README.md ================================================ # Sentinel Parameter Flow Control This component provides functionality of flow control by frequent ("hot spot") parameters. ## Usage To use Sentinel Parameter Flow Control, you need to add the following dependency to `pom.xml`: ```xml com.alibaba.csp sentinel-parameter-flow-control x.y.z ``` First you need to pass parameters with the following `SphU.entry` overloaded methods: ```java public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException ``` For example, if there are two parameters to provide, you can: ```java // paramA in index 0, paramB in index 1. SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB); ``` Then you can configure parameter flow control rules via `loadRules` method in `ParamFlowRuleManager`: ```java // QPS mode, threshold is 5 for every frequent "hot spot" parameter in index 0 (the first arg). ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY) .setParamIdx(0) .setCount(5); // We can set threshold count for specific parameter value individually. // Here we add an exception item. That means: QPS threshold of entries with parameter `PARAM_B` (type: int) // in index 0 will be 10, rather than the global threshold (5). ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)) .setClassType(int.class.getName()) .setCount(10); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); ``` The description for fields of `ParamFlowRule`: | Field | Description | Default | | :---: | :--- | :--- | | resource | resource name (**required**) | | | count | flow control threshold (**required**) | | | grade | metric type (QPS or thread count) | QPS mode | | paramIdx | the index of provided parameter in `SphU.entry(xxx, args)` (**required**) | | | paramFlowItemList | the exception items of parameter; you can set threshold to a specific parameter value | | Now the parameter flow control rules will take effect. ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/pom.xml ================================================ com.alibaba.csp sentinel-extension ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-parameter-flow-control jar com.alibaba.csp sentinel-core com.alibaba.csp sentinel-transport-common provided com.googlecode.concurrentlinkedhashmap concurrentlinkedhashmap-lru 1.4.2 junit junit test org.mockito mockito-inline test ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/command/handler/GetParamFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.fastjson.JSON; /** * @author Eric Zhao * @since 0.2.0 */ @CommandMapping(name = "getParamFlowRules", desc = "Get all parameter flow rules") public class GetParamFlowRulesCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess(JSON.toJSONString(ParamFlowRuleManager.getRules())); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyParamFlowRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.net.URLDecoder; import java.util.List; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONArray; /** * @author Eric Zhao * @since 0.2.0 */ @CommandMapping(name = "setParamFlowRules", desc = "Set parameter flow rules, while previous rules will be replaced.") public class ModifyParamFlowRulesCommandHandler implements CommandHandler { private static WritableDataSource> paramFlowWds = null; @Override public CommandResponse handle(CommandRequest request) { String data = request.getParam("data"); if (StringUtil.isBlank(data)) { return CommandResponse.ofFailure(new IllegalArgumentException("Bad data")); } try { data = URLDecoder.decode(data, "utf-8"); } catch (Exception e) { RecordLog.info("Decode rule data error", e); return CommandResponse.ofFailure(e, "decode rule data error"); } RecordLog.info("[API Server] Receiving rule change (type:parameter flow rule): {}", data); String result = SUCCESS_MSG; List flowRules = JSONArray.parseArray(data, ParamFlowRule.class); ParamFlowRuleManager.loadRules(flowRules); if (!writeToDataSource(paramFlowWds, flowRules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } /** * Write target value to given data source. * * @param dataSource writable data source * @param value target value to save * @param value type * @return true if write successful or data source is empty; false if error occurs */ private boolean writeToDataSource(WritableDataSource dataSource, T value) { if (dataSource != null) { try { dataSource.write(value); } catch (Exception e) { RecordLog.warn("Write data source failed", e); return false; } } return true; } public synchronized static WritableDataSource> getWritableDataSource() { return paramFlowWds; } public synchronized static void setWritableDataSource(WritableDataSource> hotParamWds) { ModifyParamFlowRulesCommandHandler.paramFlowWds = hotParamWds; } private static final String SUCCESS_MSG = "success"; private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)"; } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/init/ParamFlowStatisticSlotCallbackInit.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.init; import com.alibaba.csp.sentinel.slots.statistic.ParamFlowStatisticEntryCallback; import com.alibaba.csp.sentinel.slots.statistic.ParamFlowStatisticExitCallback; import com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry; /** * Init function for adding callbacks to {@link StatisticSlotCallbackRegistry} to record metrics * for frequent parameters in {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot}. * * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowStatisticSlotCallbackInit implements InitFunc { @Override public void init() { StatisticSlotCallbackRegistry.addEntryCallback(ParamFlowStatisticEntryCallback.class.getName(), new ParamFlowStatisticEntryCallback()); StatisticSlotCallbackRegistry.addExitCallback(ParamFlowStatisticExitCallback.class.getName(), new ParamFlowStatisticExitCallback()); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/HotParamSlotChainBuilder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; /** * @author Eric Zhao * @since 0.2.0 * * @deprecated since 1.7.2, we can use @Spi(order = -3000) to adjust the order of {@link ParamFlowSlot}, * this class is reserved for compatibility with older versions. * * @see ParamFlowSlot * @see DefaultSlotChainBuilder */ @Deprecated public class HotParamSlotChainBuilder extends DefaultSlotChainBuilder { } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowArgument.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; /** * ParamFlowArgument */ public interface ParamFlowArgument { /** * @return the object as a key of param flow limit */ Object paramFlowKey(); } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.util.TimeUtil; import java.lang.reflect.Array; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; /** * Rule checker for parameter flow control. * * @author Eric Zhao * @since 0.2.0 */ public final class ParamFlowChecker { public static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count, Object... args) { if (args == null) { return true; } int paramIdx = rule.getParamIdx(); if (args.length <= paramIdx) { return true; } // Get parameter value. Object value = args[paramIdx]; // Assign value with the result of paramFlowKey method if (value instanceof ParamFlowArgument) { value = ((ParamFlowArgument) value).paramFlowKey(); } // If value is null, then pass if (value == null) { return true; } if (rule.isClusterMode() && rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { return passClusterCheck(resourceWrapper, rule, count, value); } return passLocalCheck(resourceWrapper, rule, count, value); } private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { try { if (Collection.class.isAssignableFrom(value.getClass())) { for (Object param : ((Collection) value)) { if (!passSingleValueCheck(resourceWrapper, rule, count, param)) { return false; } } } else if (value.getClass().isArray()) { int length = Array.getLength(value); for (int i = 0; i < length; i++) { Object param = Array.get(value, i); if (!passSingleValueCheck(resourceWrapper, rule, count, param)) { return false; } } } else { return passSingleValueCheck(resourceWrapper, rule, count, value); } } catch (Throwable e) { RecordLog.warn("[ParamFlowChecker] Unexpected error", e); } return true; } static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) { if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { if (rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) { return passThrottleLocalCheck(resourceWrapper, rule, acquireCount, value); } else { return passDefaultLocalCheck(resourceWrapper, rule, acquireCount, value); } } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) { Set exclusionItems = rule.getParsedHotItems().keySet(); long threadCount = getParameterMetric(resourceWrapper).getThreadCount(rule.getParamIdx(), value); if (exclusionItems.contains(value)) { int itemThreshold = rule.getParsedHotItems().get(value); return ++threadCount <= itemThreshold; } long threshold = (long) rule.getCount(); return ++threadCount <= threshold; } return true; } static boolean passDefaultLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) { ParameterMetric metric = getParameterMetric(resourceWrapper); CacheMap> tokenCounters = metric == null ? null : metric.getRuleStampedTokenCounter(rule); DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME; if (tokenCounters == null) { return true; } // Calculate max token count (threshold) Set exclusionItems = rule.getParsedHotItems().keySet(); long tokenCount = (long) rule.getCount(); if (exclusionItems.contains(value)) { tokenCount = rule.getParsedHotItems().get(value); } if (tokenCount == 0) { return false; } long maxCount = tokenCount + rule.getBurstCount(); if (acquireCount > maxCount) { return false; } while (true) { long currentTime = TimeUtil.currentTimeMillis(); AtomicReference atomicLastStatus = tokenCounters.putIfAbsent(value, new AtomicReference<>( new TokenUpdateStatus(currentTime, maxCount - acquireCount) )); if (atomicLastStatus == null) { // Token never added, just replenish the tokens and consume {@code acquireCount} immediately. return true; } // Calculate the time duration since last token was added. TokenUpdateStatus lastStatus = atomicLastStatus.get(); long passTime = currentTime - lastStatus.getLastAddTokenTime(); // A simplified token bucket algorithm that will replenish the tokens only when statistic window has passed. long newQps; if (passTime > rule.getDurationInSec() * 1000) { long restQps = lastStatus.getRestQps(); long toAddCount = (passTime * tokenCount) / (rule.getDurationInSec() * 1000); newQps = toAddCount + restQps > maxCount ? (maxCount - acquireCount) : (restQps + toAddCount - acquireCount); if (newQps < 0) { return false; } TokenUpdateStatus newStatus = new TokenUpdateStatus(currentTime, newQps); if (atomicLastStatus.compareAndSet(lastStatus, newStatus)) { return true; } Thread.yield(); } else { newQps = lastStatus.getRestQps() - acquireCount; if (newQps >= 0) { TokenUpdateStatus newStatus = new TokenUpdateStatus(lastStatus.getLastAddTokenTime(), newQps); if (atomicLastStatus.compareAndSet(lastStatus, newStatus)) { return true; } } else { return false; } Thread.yield(); } } } static boolean passThrottleLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) { ParameterMetric metric = getParameterMetric(resourceWrapper); CacheMap timeRecorderMap = metric == null ? null : metric.getRuleTimeCounter(rule); if (timeRecorderMap == null) { return true; } // Calculate max token count (threshold) Set exclusionItems = rule.getParsedHotItems().keySet(); long tokenCount = (long) rule.getCount(); if (exclusionItems.contains(value)) { tokenCount = rule.getParsedHotItems().get(value); } if (tokenCount == 0) { return false; } long costTime = Math.round(1.0 * 1000 * acquireCount * rule.getDurationInSec() / tokenCount); while (true) { long currentTime = TimeUtil.currentTimeMillis(); AtomicLong timeRecorder = timeRecorderMap.putIfAbsent(value, new AtomicLong(currentTime)); if (timeRecorder == null) { return true; } //AtomicLong timeRecorder = timeRecorderMap.get(value); long lastPassTime = timeRecorder.get(); long expectedTime = lastPassTime + costTime; if (expectedTime <= currentTime || expectedTime - currentTime < rule.getMaxQueueingTimeMs()) { AtomicLong lastPastTimeRef = timeRecorderMap.get(value); if (lastPastTimeRef.compareAndSet(lastPassTime, currentTime)) { long waitTime = expectedTime - currentTime; if (waitTime > 0) { lastPastTimeRef.set(expectedTime); try { TimeUnit.MILLISECONDS.sleep(waitTime); } catch (InterruptedException e) { RecordLog.warn("passThrottleLocalCheck: wait interrupted", e); } } return true; } else { Thread.yield(); } } else { return false; } } } private static ParameterMetric getParameterMetric(ResourceWrapper resourceWrapper) { // Should not be null. return ParameterMetricStorage.getParamMetric(resourceWrapper); } @SuppressWarnings("unchecked") private static Collection toCollection(Object value) { if (value instanceof Collection) { return (Collection) value; } else if (value.getClass().isArray()) { List params = new ArrayList(); int length = Array.getLength(value); for (int i = 0; i < length; i++) { Object param = Array.get(value, i); params.add(param); } return params; } else { return Collections.singletonList(value); } } private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { try { Collection params = toCollection(value); TokenService clusterService = pickClusterService(); if (clusterService == null) { // No available cluster client or server, fallback to local or // pass in need. return fallbackToLocalOrPass(resourceWrapper, rule, count, params); } TokenResult result = clusterService.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); switch (result.getStatus()) { case TokenResultStatus.OK: return true; case TokenResultStatus.BLOCKED: return false; default: return fallbackToLocalOrPass(resourceWrapper, rule, count, params); } } catch (Throwable ex) { RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); return fallbackToLocalOrPass(resourceWrapper, rule, count, value); } } private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { return passLocalCheck(resourceWrapper, rule, count, value); } else { // The rule won't be activated, just pass. return true; } } private static TokenService pickClusterService() { if (ClusterStateManager.isClient()) { return TokenClientProvider.getClient(); } if (ClusterStateManager.isServer()) { return EmbeddedClusterTokenServerProvider.getServer(); } return null; } private ParamFlowChecker() { } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** * Parameter flow rule config in cluster mode. * * @author Eric Zhao * @since 1.4.0 */ public class ParamFlowClusterConfig { /** * Global unique ID. */ private Long flowId; /** * Threshold type (average by local value or global value). */ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; private boolean fallbackToLocalWhenFail = false; private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT; /** * The time interval length of the statistic sliding window (in milliseconds) */ private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; public Long getFlowId() { return flowId; } public ParamFlowClusterConfig setFlowId(Long flowId) { this.flowId = flowId; return this; } public int getThresholdType() { return thresholdType; } public ParamFlowClusterConfig setThresholdType(int thresholdType) { this.thresholdType = thresholdType; return this; } public boolean isFallbackToLocalWhenFail() { return fallbackToLocalWhenFail; } public ParamFlowClusterConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) { this.fallbackToLocalWhenFail = fallbackToLocalWhenFail; return this; } public int getSampleCount() { return sampleCount; } public ParamFlowClusterConfig setSampleCount(int sampleCount) { this.sampleCount = sampleCount; return this; } public int getWindowIntervalMs() { return windowIntervalMs; } public ParamFlowClusterConfig setWindowIntervalMs(int windowIntervalMs) { this.windowIntervalMs = windowIntervalMs; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ParamFlowClusterConfig config = (ParamFlowClusterConfig)o; if (thresholdType != config.thresholdType) { return false; } if (fallbackToLocalWhenFail != config.fallbackToLocalWhenFail) { return false; } if (sampleCount != config.sampleCount) { return false; } if (windowIntervalMs != config.windowIntervalMs) { return false; } return flowId != null ? flowId.equals(config.flowId) : config.flowId == null; } @Override public int hashCode() { int result = flowId != null ? flowId.hashCode() : 0; result = 31 * result + thresholdType; result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); result = 31 * result + sampleCount; result = 31 * result + windowIntervalMs; return result; } @Override public String toString() { return "ParamFlowClusterConfig{" + "flowId=" + flowId + ", thresholdType=" + thresholdType + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + ", sampleCount=" + sampleCount + ", windowIntervalMs=" + windowIntervalMs + '}'; } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * Block exception for frequent ("hot-spot") parameter flow control. * * @author jialiang.linjl * @since 0.2.0 */ public class ParamFlowException extends BlockException { private final String resourceName; public ParamFlowException(String resourceName, String message, Throwable cause) { super(message, cause); this.resourceName = resourceName; } public ParamFlowException(String resourceName, String param) { super(param, param); this.resourceName = resourceName; } public ParamFlowException(String resourceName, String param, ParamFlowRule rule) { super(param, param); this.resourceName = resourceName; this.rule = rule; } public String getResourceName() { return resourceName; } @Override public Throwable fillInStackTrace() { return this; } /** * Get the parameter value that triggered the parameter flow control. * * @return the parameter value * @since 1.4.2 */ public String getLimitParam() { return getMessage(); } /** * Get triggered rule. * Note: the rule result is a reference to rule map and SHOULD NOT be modified. * * @return triggered rule * @since 1.4.2 */ @Override public ParamFlowRule getRule() { return rule.as(ParamFlowRule.class); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowItem.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; /** * A flow control item for a specific parameter value. * * @author jialiang.linjl * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowItem { private String object; private Integer count; private String classType; public ParamFlowItem() {} public ParamFlowItem(String object, Integer count, String classType) { this.object = object; this.count = count; this.classType = classType; } public static ParamFlowItem newItem(T object, Integer count) { if (object == null) { throw new IllegalArgumentException("Invalid object: null"); } return new ParamFlowItem(object.toString(), count, object.getClass().getName()); } public String getObject() { return object; } public ParamFlowItem setObject(String object) { this.object = object; return this; } public Integer getCount() { return count; } public ParamFlowItem setCount(Integer count) { this.count = count; return this; } public String getClassType() { return classType; } public ParamFlowItem setClassType(String classType) { this.classType = classType; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ParamFlowItem item = (ParamFlowItem)o; if (object != null ? !object.equals(item.object) : item.object != null) { return false; } if (count != null ? !count.equals(item.count) : item.count != null) { return false; } return classType != null ? classType.equals(item.classType) : item.classType == null; } @Override public int hashCode() { int result = object != null ? object.hashCode() : 0; result = 31 * result + (count != null ? count.hashCode() : 0); result = 31 * result + (classType != null ? classType.hashCode() : 0); return result; } @Override public String toString() { return "ParamFlowItem{" + "object=" + object + ", count=" + count + ", classType='" + classType + '\'' + '}'; } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** * Rules for "hot-spot" frequent parameter flow control. * * @author jialiang.linjl * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowRule extends AbstractRule { public ParamFlowRule() {} public ParamFlowRule(String resourceName) { setResource(resourceName); } /** * The threshold type of flow control (0: thread count, 1: QPS). */ private int grade = RuleConstant.FLOW_GRADE_QPS; /** * Parameter index. */ private Integer paramIdx; /** * The threshold count. */ private double count; /** * Traffic shaping behavior (since 1.6.0). */ private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT; private int maxQueueingTimeMs = 0; private int burstCount = 0; private long durationInSec = 1; /** * Original exclusion items of parameters. */ private List paramFlowItemList = new ArrayList(); /** * Parsed exclusion items of parameters. Only for internal use. */ private Map hotItems = new HashMap(); /** * Indicating whether the rule is for cluster mode. */ private boolean clusterMode = false; /** * Cluster mode specific config for parameter flow rule. */ private ParamFlowClusterConfig clusterConfig; public int getControlBehavior() { return controlBehavior; } public ParamFlowRule setControlBehavior(int controlBehavior) { this.controlBehavior = controlBehavior; return this; } public int getMaxQueueingTimeMs() { return maxQueueingTimeMs; } public ParamFlowRule setMaxQueueingTimeMs(int maxQueueingTimeMs) { this.maxQueueingTimeMs = maxQueueingTimeMs; return this; } public int getBurstCount() { return burstCount; } public ParamFlowRule setBurstCount(int burstCount) { this.burstCount = burstCount; return this; } public long getDurationInSec() { return durationInSec; } public ParamFlowRule setDurationInSec(long durationInSec) { this.durationInSec = durationInSec; return this; } public int getGrade() { return grade; } public ParamFlowRule setGrade(int grade) { this.grade = grade; return this; } public Integer getParamIdx() { return paramIdx; } public ParamFlowRule setParamIdx(Integer paramIdx) { this.paramIdx = paramIdx; return this; } public double getCount() { return count; } public ParamFlowRule setCount(double count) { this.count = count; return this; } public List getParamFlowItemList() { return paramFlowItemList; } public ParamFlowRule setParamFlowItemList(List paramFlowItemList) { this.paramFlowItemList = paramFlowItemList; return this; } public Integer retrieveExclusiveItemCount(Object value) { if (value == null || hotItems == null) { return null; } return hotItems.get(value); } Map getParsedHotItems() { return hotItems; } ParamFlowRule setParsedHotItems(Map hotItems) { this.hotItems = hotItems; return this; } public boolean isClusterMode() { return clusterMode; } public ParamFlowRule setClusterMode(boolean clusterMode) { this.clusterMode = clusterMode; return this; } public ParamFlowClusterConfig getClusterConfig() { return clusterConfig; } public ParamFlowRule setClusterConfig(ParamFlowClusterConfig clusterConfig) { this.clusterConfig = clusterConfig; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } ParamFlowRule that = (ParamFlowRule)o; if (grade != that.grade) { return false; } if (Double.compare(that.count, count) != 0) { return false; } if (controlBehavior != that.controlBehavior) { return false; } if (maxQueueingTimeMs != that.maxQueueingTimeMs) { return false; } if (burstCount != that.burstCount) { return false; } if (durationInSec != that.durationInSec) { return false; } if (clusterMode != that.clusterMode) { return false; } if (!Objects.equals(paramIdx, that.paramIdx)) { return false; } if (!Objects.equals(paramFlowItemList, that.paramFlowItemList)) { return false; } return Objects.equals(clusterConfig, that.clusterConfig); } @Override public int hashCode() { int result = super.hashCode(); long temp; result = 31 * result + grade; result = 31 * result + (paramIdx != null ? paramIdx.hashCode() : 0); temp = Double.doubleToLongBits(count); result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + controlBehavior; result = 31 * result + maxQueueingTimeMs; result = 31 * result + burstCount; result = 31 * result + (int)(durationInSec ^ (durationInSec >>> 32)); result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0); result = 31 * result + (clusterMode ? 1 : 0); result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0); return result; } @Override public String toString() { return "ParamFlowRule{" + "grade=" + grade + ", paramIdx=" + paramIdx + ", count=" + count + ", controlBehavior=" + controlBehavior + ", maxQueueingTimeMs=" + maxQueueingTimeMs + ", burstCount=" + burstCount + ", durationInSec=" + durationInSec + ", paramFlowItemList=" + paramFlowItemList + ", clusterMode=" + clusterMode + ", clusterConfig=" + clusterConfig + '}'; } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Manager for frequent ("hot-spot") parameter flow rules. * * @author jialiang.linjl * @author Eric Zhao * @since 0.2.0 */ public final class ParamFlowRuleManager { private static final RuleManager PARAM_FLOW_RULES = new RuleManager<>(); private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(PROPERTY_LISTENER); } /** * Load parameter flow rules. Former rules will be replaced. * * @param rules new rules to load. */ public static void loadRules(List rules) { try { currentProperty.updateValue(rules); } catch (Throwable e) { RecordLog.info("[ParamFlowRuleManager] Failed to load rules", e); } } /** * Listen to the {@link SentinelProperty} for {@link ParamFlowRule}s. The * property is the source of {@link ParamFlowRule}s. Parameter flow rules * can also be set by {@link #loadRules(List)} directly. * * @param property the property to listen */ public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (PROPERTY_LISTENER) { currentProperty.removeListener(PROPERTY_LISTENER); property.addListener(PROPERTY_LISTENER); currentProperty = property; RecordLog.info("[ParamFlowRuleManager] New property has been registered to hot param rule manager"); } } public static List getRulesOfResource(String resourceName) { return new ArrayList<>(PARAM_FLOW_RULES.getRules(resourceName)); } public static boolean hasRules(String resourceName) { return PARAM_FLOW_RULES.hasConfig(resourceName); } /** * Get a copy of the rules. * * @return a new copy of the rules. */ public static List getRules() { return PARAM_FLOW_RULES.getRules(); } static class RulePropertyListener implements PropertyListener> { @Override public void configUpdate(List list) { Map> rules = aggregateAndPrepareParamRules(list); PARAM_FLOW_RULES.updateRules(rules); RecordLog.info("[ParamFlowRuleManager] Parameter flow rules received: {}", PARAM_FLOW_RULES); } @Override public void configLoad(List list) { Map> rules = aggregateAndPrepareParamRules(list); PARAM_FLOW_RULES.updateRules(rules); RecordLog.info("[ParamFlowRuleManager] Parameter flow rules received: {}", PARAM_FLOW_RULES); } private Map> aggregateAndPrepareParamRules(List list) { Map> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(list); if (newRuleMap == null || newRuleMap.isEmpty()) { // No parameter flow rules, so clear all the metrics. ParameterMetricStorage.getMetricsMap().clear(); RecordLog.info("[ParamFlowRuleManager] No parameter flow rules, clearing all parameter metrics"); return newRuleMap; } // Clear unused parameter metrics. for (Map.Entry> entry : PARAM_FLOW_RULES.getOriginalRules().entrySet()) { String resource = entry.getKey(); if (!newRuleMap.containsKey(resource)) { ParameterMetricStorage.clearParamMetricForResource(resource); continue; } List newRuleList = newRuleMap.get(resource); List oldRuleList = new ArrayList<>(entry.getValue()); oldRuleList.removeAll(newRuleList); for (ParamFlowRule rule : oldRuleList) { ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetricForResource(resource); if (parameterMetric != null) { parameterMetric.clearForRule(rule); } } } return newRuleMap; } } private ParamFlowRuleManager() {} } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; /** * @author Eric Zhao */ public final class ParamFlowRuleUtil { /** * Check whether the provided rule is valid. * * @param rule any parameter rule * @return true if valid, otherwise false */ public static boolean isValidRule(ParamFlowRule rule) { return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 && rule.getGrade() >= 0 && rule.getParamIdx() != null && rule.getBurstCount() >= 0 && rule.getControlBehavior() >= 0 && rule.getDurationInSec() > 0 && rule.getMaxQueueingTimeMs() >= 0 && checkCluster(rule) & checkRegexField(rule); } private static boolean checkCluster(/*@PreChecked*/ ParamFlowRule rule) { if (!rule.isClusterMode()) { return true; } ParamFlowClusterConfig clusterConfig = rule.getClusterConfig(); if (clusterConfig == null) { return false; } if (!FlowRuleUtil.isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) { return false; } return validClusterRuleId(clusterConfig.getFlowId()); } private static boolean checkRegexField(ParamFlowRule rule) { if (!RuleManager.checkRegexResourceField(rule)) { return false; } if (rule.isRegex()) { return !rule.isClusterMode() && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_DEFAULT; } return true; } public static boolean validClusterRuleId(Long id) { return id != null && id > 0; } /** * Fill the parameter rule with parsed items. * * @param rule valid parameter rule */ public static void fillExceptionFlowItems(ParamFlowRule rule) { if (rule != null) { if (rule.getParamFlowItemList() == null) { rule.setParamFlowItemList(new ArrayList()); } Map itemMap = parseHotItems(rule.getParamFlowItemList()); rule.setParsedHotItems(itemMap); } } /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * * @param list raw list of flow rules * @return constructed new flow rule map; empty map if list is null or empty, or no valid rules * @since 1.6.1 */ public static Map> buildParamRuleMap(List list) { return buildParamRuleMap(list, null); } /** * Build the parameter flow rule map from raw list of rules, grouping by resource name. * * @param list raw list of parameter flow rules * @param filter rule filter * @return constructed new parameter flow rule map; empty map if list is null or empty, or no wanted rules * @since 1.6.1 */ public static Map> buildParamRuleMap(List list, Predicate filter) { return buildParamRuleMap(list, filter, true); } /** * Build the parameter flow rule map from raw list of rules, grouping by resource name. * * @param list raw list of parameter flow rules * @param filter rule filter * @param shouldSort whether the rules should be sorted * @return constructed new parameter flow rule map; empty map if list is null or empty, or no wanted rules * @since 1.6.1 */ public static Map> buildParamRuleMap(List list, Predicate filter, boolean shouldSort) { return buildParamRuleMap(list, EXTRACT_RESOURCE, filter, shouldSort); } /** * Build the rule map from raw list of parameter flow rules, grouping by provided group function. * * @param list raw list of parameter flow rules * @param groupFunction grouping function of the map (by key) * @param filter rule filter * @param shouldSort whether the rules should be sorted * @param type of key * @return constructed new rule map; empty map if list is null or empty, or no wanted rules * @since 1.6.1 */ public static Map> buildParamRuleMap(List list, Function groupFunction, Predicate filter, boolean shouldSort) { AssertUtil.notNull(groupFunction, "groupFunction should not be null"); Map> newRuleMap = new ConcurrentHashMap<>(); if (list == null || list.isEmpty()) { return newRuleMap; } Map> tmpMap = new ConcurrentHashMap<>(); for (ParamFlowRule rule : list) { if (!ParamFlowRuleUtil.isValidRule(rule)) { RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule); continue; } if (filter != null && !filter.test(rule)) { continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } ParamFlowRuleUtil.fillExceptionFlowItems(rule); K key = groupFunction.apply(rule); if (key == null) { continue; } Set flowRules = tmpMap.get(key); if (flowRules == null) { // Use hash set here to remove duplicate rules. flowRules = new HashSet<>(); tmpMap.put(key, flowRules); } flowRules.add(rule); } for (Entry> entries : tmpMap.entrySet()) { List rules = new ArrayList<>(entries.getValue()); if (shouldSort) { // TODO: Sort the rules. } newRuleMap.put(entries.getKey(), rules); } return newRuleMap; } static Map parseHotItems(List items) { if (items == null || items.isEmpty()) { return new HashMap<>(); } Map itemMap = new HashMap<>(items.size()); for (ParamFlowItem item : items) { // Value should not be null. Object value; try { value = parseItemValue(item.getObject(), item.getClassType()); } catch (Exception ex) { RecordLog.warn("[ParamFlowRuleUtil] Failed to parse value for item: " + item, ex); continue; } if (item.getCount() == null || item.getCount() < 0 || value == null) { RecordLog.warn("[ParamFlowRuleUtil] Ignoring invalid exclusion parameter item: " + item); continue; } itemMap.put(value, item.getCount()); } return itemMap; } static Object parseItemValue(String value, String classType) { if (value == null) { throw new IllegalArgumentException("Null value"); } if (StringUtil.isBlank(classType)) { // If the class type is not provided, then treat it as string. return value; } // Handle primitive type. if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) { return Integer.parseInt(value); } else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) { return Boolean.parseBoolean(value); } else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) { return Long.parseLong(value); } else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) { return Double.parseDouble(value); } else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) { return Float.parseFloat(value); } else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) { return Byte.parseByte(value); } else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) { return Short.parseShort(value); } else if (char.class.toString().equals(classType)) { char[] array = value.toCharArray(); return array.length > 0 ? array[0] : null; } return value; } private static final Function EXTRACT_RESOURCE = new Function() { @Override public String apply(ParamFlowRule rule) { return rule.getResource(); } }; private ParamFlowRuleUtil() {} } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowSlot.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.spi.Spi; import java.util.List; /** * A processor slot that is responsible for flow control by frequent ("hot spot") parameters. * * @author jialiang.linjl * @author Eric Zhao * @since 0.2.0 */ @Spi(order = -3000) public class ParamFlowSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { checkFlow(resourceWrapper, count, args); fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } void applyRealParamIdx(/*@NonNull*/ ParamFlowRule rule, int length) { int paramIdx = rule.getParamIdx(); if (paramIdx < 0) { if (-paramIdx <= length) { rule.setParamIdx(length + paramIdx); } else { // Illegal index, give it a illegal positive value, latter rule checking will pass. rule.setParamIdx(-paramIdx); } } } void checkFlow(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { if (args == null) { return; } if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) { return; } List rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName()); for (ParamFlowRule rule : rules) { applyRealParamIdx(rule, args.length); // Initialize the parameter metrics. ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule); if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) { String triggeredParam = ""; if (args.length > rule.getParamIdx()) { Object value = args[rule.getParamIdx()]; // Assign actual value with the result of paramFlowKey method if (value instanceof ParamFlowArgument) { value = ((ParamFlowArgument) value).paramFlowKey(); } triggeredParam = String.valueOf(value); } throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule); } } } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParameterMetric.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.lang.reflect.Array; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; /** * Metrics for frequent ("hot spot") parameters. * * @author Eric Zhao * @since 0.2.0 */ public class ParameterMetric { private static final int THREAD_COUNT_MAX_CAPACITY = 4000; private static final int BASE_PARAM_MAX_CAPACITY = 4000; private static final int TOTAL_MAX_CAPACITY = 20_0000; private final Object lock = new Object(); /** * Format: (rule, (value, timeRecorder)) * * @since 1.6.0 */ private final Map> ruleTimeCounters = new HashMap<>(); /** * Format: (rule, (value, tokenCounter)) * * @since 1.6.0 */ private final Map>> ruleTokenCounter = new HashMap<>(); private final Map> threadCountMap = new HashMap<>(); /** * Get the token counter for given parameter rule. * * @param rule valid parameter rule * @return the associated token counter * @since 1.8.8 */ CacheMap> getRuleStampedTokenCounter(ParamFlowRule rule) { return ruleTokenCounter.get(rule); } public void clear() { synchronized (lock) { ruleTimeCounters.clear(); ruleTokenCounter.clear(); threadCountMap.clear(); } } /** * Get the time record counter for given parameter rule. * * @param rule valid parameter rule * @return the associated time counter * @since 1.6.0 */ public CacheMap getRuleTimeCounter(ParamFlowRule rule) { return ruleTimeCounters.get(rule); } public void clearForRule(ParamFlowRule rule) { synchronized (lock) { ruleTimeCounters.remove(rule); ruleTokenCounter.remove(rule); threadCountMap.remove(rule.getParamIdx()); } } public void initialize(ParamFlowRule rule) { if (!ruleTimeCounters.containsKey(rule)) { synchronized (lock) { if (ruleTimeCounters.get(rule) == null) { long size = Math.min(BASE_PARAM_MAX_CAPACITY * rule.getDurationInSec(), TOTAL_MAX_CAPACITY); ruleTimeCounters.put(rule, new ConcurrentLinkedHashMapWrapper(size)); } } } if (!ruleTokenCounter.containsKey(rule)) { synchronized (lock) { if (ruleTokenCounter.get(rule) == null) { long size = Math.min(BASE_PARAM_MAX_CAPACITY * rule.getDurationInSec(), TOTAL_MAX_CAPACITY); ruleTokenCounter.put(rule, new ConcurrentLinkedHashMapWrapper<>(size)); } } } if (!threadCountMap.containsKey(rule.getParamIdx())) { synchronized (lock) { if (threadCountMap.get(rule.getParamIdx()) == null) { threadCountMap.put(rule.getParamIdx(), new ConcurrentLinkedHashMapWrapper(THREAD_COUNT_MAX_CAPACITY)); } } } } @SuppressWarnings("rawtypes") public void decreaseThreadCount(Object... args) { if (args == null) { return; } try { for (int index = 0; index < args.length; index++) { CacheMap threadCount = threadCountMap.get(index); if (threadCount == null) { continue; } Object arg = args[index]; if (arg == null) { continue; } if (Collection.class.isAssignableFrom(arg.getClass())) { for (Object value : ((Collection)arg)) { AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger()); if (oldValue != null) { int currentValue = oldValue.decrementAndGet(); if (currentValue <= 0) { threadCount.remove(value); } } } } else if (arg.getClass().isArray()) { int length = Array.getLength(arg); for (int i = 0; i < length; i++) { Object value = Array.get(arg, i); AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger()); if (oldValue != null) { int currentValue = oldValue.decrementAndGet(); if (currentValue <= 0) { threadCount.remove(value); } } } } else { AtomicInteger oldValue = threadCount.putIfAbsent(arg, new AtomicInteger()); if (oldValue != null) { int currentValue = oldValue.decrementAndGet(); if (currentValue <= 0) { threadCount.remove(arg); } } } } } catch (Throwable e) { RecordLog.warn("[ParameterMetric] Param exception", e); } } @SuppressWarnings("rawtypes") public void addThreadCount(Object... args) { if (args == null) { return; } try { for (int index = 0; index < args.length; index++) { CacheMap threadCount = threadCountMap.get(index); if (threadCount == null) { continue; } Object arg = args[index]; if (arg == null) { continue; } if (Collection.class.isAssignableFrom(arg.getClass())) { for (Object value : ((Collection)arg)) { AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger()); if (oldValue != null) { oldValue.incrementAndGet(); } else { threadCount.put(value, new AtomicInteger(1)); } } } else if (arg.getClass().isArray()) { int length = Array.getLength(arg); for (int i = 0; i < length; i++) { Object value = Array.get(arg, i); AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger()); if (oldValue != null) { oldValue.incrementAndGet(); } else { threadCount.put(value, new AtomicInteger(1)); } } } else { AtomicInteger oldValue = threadCount.putIfAbsent(arg, new AtomicInteger()); if (oldValue != null) { oldValue.incrementAndGet(); } else { threadCount.put(arg, new AtomicInteger(1)); } } } } catch (Throwable e) { RecordLog.warn("[ParameterMetric] Param exception", e); } } public long getThreadCount(int index, Object value) { CacheMap cacheMap = threadCountMap.get(index); if (cacheMap == null) { return 0; } AtomicInteger count = cacheMap.get(value); return count == null ? 0L : count.get(); } /** * Get the token counter map. Package-private for test. * * @return the token counter map */ Map>> getRuleTokenCounterMap() { return ruleTokenCounter; } Map> getThreadCountMap() { return threadCountMap; } Map> getRuleTimeCounterMap() { return ruleTimeCounters; } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParameterMetricStorage.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao * @since 1.6.1 */ public final class ParameterMetricStorage { private static final Map metricsMap = new ConcurrentHashMap<>(); /** * Lock for a specific resource. */ private static final Object LOCK = new Object(); /** * Init the parameter metric and index map for given resource. * Package-private for test. * * @param resourceWrapper resource to init * @param rule relevant rule */ public static void initParamMetricsFor(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule) { if (resourceWrapper == null || resourceWrapper.getName() == null) { return; } String resourceName = resourceWrapper.getName(); ParameterMetric metric; // Assume that the resource is valid. if ((metric = metricsMap.get(resourceName)) == null) { synchronized (LOCK) { if ((metric = metricsMap.get(resourceName)) == null) { metric = new ParameterMetric(); metricsMap.put(resourceWrapper.getName(), metric); RecordLog.info("[ParameterMetricStorage] Creating parameter metric for: {}", resourceWrapper.getName()); } } } metric.initialize(rule); } public static ParameterMetric getParamMetric(ResourceWrapper resourceWrapper) { if (resourceWrapper == null || resourceWrapper.getName() == null) { return null; } return metricsMap.get(resourceWrapper.getName()); } public static ParameterMetric getParamMetricForResource(String resourceName) { if (resourceName == null) { return null; } return metricsMap.get(resourceName); } public static void clearParamMetricForResource(String resourceName) { if (StringUtil.isBlank(resourceName)) { return; } metricsMap.remove(resourceName); RecordLog.info("[ParameterMetricStorage] Clearing parameter metric for: {}", resourceName); } static Map getMetricsMap() { return metricsMap; } private ParameterMetricStorage() {} } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/RollingParamEvent.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; /** * @author Eric Zhao * @since 0.2.0 */ public enum RollingParamEvent { /** * Indicates that the request successfully passed the slot chain (entry). */ REQUEST_PASSED, /** * Indicates that the request is blocked by a specific slot. */ REQUEST_BLOCKED } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/TokenUpdateStatus.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow.param; class TokenUpdateStatus { private final long lastAddTokenTime; private final long restQps; public TokenUpdateStatus(long lastAddTokenTime, long restQps) { this.lastAddTokenTime = lastAddTokenTime; this.restQps = restQps; } public long getLastAddTokenTime() { return lastAddTokenTime; } public long getRestQps() { return restQps; } @Override public String toString() { return "TokenUpdateStatus{" + "hash=" + System.identityHashCode(this) + ", lastAddTokenTime=" + lastAddTokenTime + ", requestCount=" + restQps + '}'; } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/ParamFlowStatisticEntryCallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; /** * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowStatisticEntryCallback implements ProcessorSlotEntryCallback { @Override public void onPass(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) { // The "hot spot" parameter metric is present only if parameter flow rules for the resource exist. ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetric(resourceWrapper); if (parameterMetric != null) { parameterMetric.addThreadCount(args); } } @Override public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, Object... args) { // Here we don't add block count here because checking the type of block exception can affect performance. // We add the block count when throwing the ParamFlowException instead. } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/ParamFlowStatisticExitCallback.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; /** * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowStatisticExitCallback implements ProcessorSlotExitCallback { @Override public void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { if (context.getCurEntry().getBlockError() == null) { ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetric(resourceWrapper); if (parameterMetric != null) { parameterMetric.decreaseThreadCount(args); } } } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/CacheMap.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.cache; import java.util.Set; /** * A common cache map interface. * * @param type of the key * @param type of the value * @author Eric Zhao * @since 0.2.0 */ public interface CacheMap { boolean containsKey(K key); V get(K key); V remove(K key); V put(K key, V value); V putIfAbsent(K key, V value); long size(); void clear(); Set keySet(boolean ascending); } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/ConcurrentLinkedHashMapWrapper.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.cache; import java.util.Set; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.googlecode.concurrentlinkedhashmap.Weighers; /** * A {@link ConcurrentLinkedHashMap} wrapper for the universal {@link CacheMap}. * * @author Eric Zhao * @since 0.2.0 */ public class ConcurrentLinkedHashMapWrapper implements CacheMap { private static final int DEFAULT_CONCURRENCY_LEVEL = 16; private final ConcurrentLinkedHashMap map; public ConcurrentLinkedHashMapWrapper(long size) { if (size <= 0) { throw new IllegalArgumentException("Cache max capacity should be positive: " + size); } this.map = new ConcurrentLinkedHashMap.Builder() .concurrencyLevel(DEFAULT_CONCURRENCY_LEVEL) .maximumWeightedCapacity(size) .weigher(Weighers.singleton()) .build(); } public ConcurrentLinkedHashMapWrapper(ConcurrentLinkedHashMap map) { if (map == null) { throw new IllegalArgumentException("Invalid map instance"); } this.map = map; } @Override public boolean containsKey(T key) { return map.containsKey(key); } @Override public R get(T key) { return map.get(key); } @Override public R remove(T key) { return map.remove(key); } @Override public R put(T key, R value) { return map.put(key, value); } @Override public R putIfAbsent(T key, R value) { return map.putIfAbsent(key, value); } @Override public long size() { return map.weightedSize(); } @Override public void clear() { map.clear(); } @Override public Set keySet(boolean ascending) { if (ascending) { return map.ascendingKeySet(); } else { return map.descendingKeySet(); } } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucket.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.data; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.util.AssertUtil; /** * Represents metric bucket of frequent parameters in a period of time window. * * @author Eric Zhao * @since 0.2.0 */ public class ParamMapBucket { private final CacheMap[] data; public ParamMapBucket() { this(DEFAULT_MAX_CAPACITY); } @SuppressWarnings("unchecked") public ParamMapBucket(int capacity) { AssertUtil.isTrue(capacity > 0, "capacity should be positive"); RollingParamEvent[] events = RollingParamEvent.values(); this.data = new CacheMap[events.length]; for (RollingParamEvent event : events) { data[event.ordinal()] = new ConcurrentLinkedHashMapWrapper(capacity); } } public void reset() { for (RollingParamEvent event : RollingParamEvent.values()) { data[event.ordinal()].clear(); } } public int get(RollingParamEvent event, Object value) { AtomicInteger counter = data[event.ordinal()].get(value); return counter == null ? 0 : counter.intValue(); } public ParamMapBucket add(RollingParamEvent event, int count, Object value) { AtomicInteger counter = data[event.ordinal()].get(value); // Note: not strictly concise. if (counter == null) { AtomicInteger old = data[event.ordinal()].putIfAbsent(value, new AtomicInteger(count)); if (old != null) { old.addAndGet(count); } } else { counter.addAndGet(count); } return this; } public Set ascendingKeySet(RollingParamEvent type) { return data[type.ordinal()].keySet(true); } public Set descendingKeySet(RollingParamEvent type) { return data[type.ordinal()].keySet(false); } public static final int DEFAULT_MAX_CAPACITY = 200; } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.command.handler.GetParamFlowRulesCommandHandler com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot ================================================ com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/block/flow/param/AbstractTimeBasedTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.block.flow.param; import com.alibaba.csp.sentinel.util.TimeUtil; import org.mockito.MockedStatic; import org.mockito.Mockito; public abstract class AbstractTimeBasedTest { private long currentMillis = 0; public MockedStatic mockTimeUtil() { MockedStatic mocked = Mockito.mockStatic(TimeUtil.class); mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); return mocked; } protected final void useActualTime(MockedStatic mocked) { mocked.when(TimeUtil::currentTimeMillis).thenCallRealMethod(); } protected final void setCurrentMillis(MockedStatic mocked, long cur) { currentMillis = cur; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleep(MockedStatic mocked, long t) { currentMillis += t; mocked.when(TimeUtil::currentTimeMillis).thenReturn(currentMillis); } protected final void sleepSecond(MockedStatic mocked, long timeSec) { sleep(mocked, timeSec * 1000); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowCheckerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test cases for {@link ParamFlowChecker}. * * @author Eric Zhao */ public class ParamFlowCheckerTest { @Test public void testHotParamCheckerPassCheckExceedArgs() { final String resourceName = "testHotParamCheckerPassCheckExceedArgs"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 1; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(10); rule.setParamIdx(paramIdx); assertTrue("The rule will pass if the paramIdx exceeds provided args", ParamFlowChecker.passCheck(resourceWrapper, rule, 1, "abc")); } @Test public void testSingleValueCheckQpsWithExceptionItems() throws InterruptedException { final String resourceName = "testSingleValueCheckQpsWithExceptionItems"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); TimeUtil.currentTimeMillis(); int paramIdx = 0; long globalThreshold = 5L; int thresholdB = 0; int thresholdD = 7; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(globalThreshold); rule.setParamIdx(paramIdx); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); String valueA = "valueA"; String valueB = "valueB"; String valueC = "valueC"; String valueD = "valueD"; // Directly set parsed map for test. Map map = new HashMap(); map.put(valueB, thresholdB); map.put(valueD, thresholdD); rule.setParsedHotItems(map); ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); TimeUnit.SECONDS.sleep(3); } @Test public void testSingleValueCheckThreadCountWithExceptionItems() { final String resourceName = "testSingleValueCheckThreadCountWithExceptionItems"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long globalThreshold = 5L; int thresholdB = 3; int thresholdD = 7; ParamFlowRule rule = new ParamFlowRule(resourceName).setCount(globalThreshold).setParamIdx(paramIdx) .setGrade(RuleConstant.FLOW_GRADE_THREAD); String valueA = "valueA"; String valueB = "valueB"; String valueC = "valueC"; String valueD = "valueD"; // Directly set parsed map for test. Map map = new HashMap(); map.put(valueB, thresholdB); map.put(valueD, thresholdD); rule.setParsedHotItems(map); ParameterMetric metric = mock(ParameterMetric.class); when(metric.getThreadCount(paramIdx, valueA)).thenReturn(globalThreshold - 1); when(metric.getThreadCount(paramIdx, valueB)).thenReturn(globalThreshold - 1); when(metric.getThreadCount(paramIdx, valueC)).thenReturn(globalThreshold - 1); when(metric.getThreadCount(paramIdx, valueD)).thenReturn(globalThreshold + 1); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); when(metric.getThreadCount(paramIdx, valueA)).thenReturn(globalThreshold); when(metric.getThreadCount(paramIdx, valueB)).thenReturn(thresholdB - 1L); when(metric.getThreadCount(paramIdx, valueC)).thenReturn(globalThreshold + 1); when(metric.getThreadCount(paramIdx, valueD)).thenReturn(globalThreshold - 1).thenReturn((long) thresholdD); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); } @Test public void testPassLocalCheckForCollection() throws InterruptedException { final String resourceName = "testPassLocalCheckForCollection"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; double globalThreshold = 1; ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx).setCount(globalThreshold); String v1 = "a", v2 = "B", v3 = "Cc"; List list = Arrays.asList(v1, v2, v3); ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list)); assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list)); } @Test public void testPassLocalCheckForArray() throws InterruptedException { final String resourceName = "testPassLocalCheckForArray"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; double globalThreshold = 1; ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER).setCount(globalThreshold); TimeUtil.currentTimeMillis(); String v1 = "a", v2 = "B", v3 = "Cc"; Object arr = new String[]{v1, v2, v3}; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr)); assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr)); } @Test public void testPassLocalCheckForComplexParam() throws InterruptedException { class User implements ParamFlowArgument { Integer id; String name; String address; public User(Integer id, String name, String address) { this.id = id; this.name = name; this.address = address; } @Override public Object paramFlowKey() { return name; } } final String resourceName = "testPassLocalCheckForComplexParam"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; double globalThreshold = 1; ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx).setCount(globalThreshold); Object[] args = new Object[]{new User(1, "Bob", "Hangzhou"), 10, "Demo"}; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, args)); assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, args)); } @Before public void setUp() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowDefaultCheckerTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.block.flow.param.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.util.TimeUtil; import org.mockito.MockedStatic; /** * @author jialiang.linjl * @author Eric Zhao */ public class ParamFlowDefaultCheckerTest extends AbstractTimeBasedTest { @Test public void testCheckQpsWithLongIntervalAndHighThreshold() { try (MockedStatic mocked = super.mockTimeUtil()) { // This test case is intended to avoid number overflow. final String resourceName = "testCheckQpsWithLongIntervalAndHighThreshold"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; // Set a large threshold. long threshold = 25000L; ParamFlowRule rule = new ParamFlowRule(resourceName) .setCount(threshold) .setParamIdx(paramIdx); String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); // We mock the time directly to avoid unstable behaviour. setCurrentMillis(mocked, System.currentTimeMillis()); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); // 24 hours passed. // This can make `toAddCount` larger that Integer.MAX_VALUE. sleep(mocked, 1000 * 60 * 60 * 24); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); // 48 hours passed. sleep(mocked, 1000 * 60 * 60 * 48); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); } } @Test public void testParamFlowDefaultCheckSingleQps() { try (MockedStatic mocked = super.mockTimeUtil()) { final String resourceName = "testParamFlowDefaultCheckSingleQps"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long threshold = 5L; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(threshold); rule.setParamIdx(paramIdx); String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); // We mock the time directly to avoid unstable behaviour. setCurrentMillis(mocked, System.currentTimeMillis()); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleep(mocked, 3000); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); } } @Test public void testParamFlowDefaultCheckSingleQpsWithBurst() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { final String resourceName = "testParamFlowDefaultCheckSingleQpsWithBurst"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long threshold = 5L; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(threshold); rule.setParamIdx(paramIdx); rule.setBurstCount(3); String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); // We mock the time directly to avoid unstable behaviour. setCurrentMillis(mocked, System.currentTimeMillis()); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleep(mocked, 1002); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleep(mocked, 1002); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleep(mocked, 2000); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleep(mocked, 1002); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); } } @Test public void testParamFlowDefaultCheckQpsInDifferentDuration() throws InterruptedException { try (MockedStatic mocked = super.mockTimeUtil()) { final String resourceName = "testParamFlowDefaultCheckQpsInDifferentDuration"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long threshold = 5L; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(threshold); rule.setParamIdx(paramIdx); rule.setDurationInSec(60); String valueA = "helloWorld"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); // We mock the time directly to avoid unstable behaviour. setCurrentMillis(mocked, System.currentTimeMillis()); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleepSecond(mocked, 1); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleepSecond(mocked, 10); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleepSecond(mocked, 30); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); sleepSecond(mocked, 30); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); } } @Test public void testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads() throws Exception { final String resourceName = "testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long threshold = 5L; final ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(threshold); rule.setParamIdx(paramIdx); rule.setDurationInSec(3); final String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<>(4000)); int threadCount = 40; final CountDownLatch waitLatch = new CountDownLatch(threadCount); final AtomicInteger successCount = new AtomicInteger(); for (int i = 0; i < threadCount; i++) { Thread t = new Thread(() -> { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount.incrementAndGet(); } waitLatch.countDown(); }); t.setName("sentinel-simulate-traffic-task-" + i); t.start(); } waitLatch.await(); assertEquals(threshold, successCount.get()); successCount.set(0); System.out.println("testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads: sleep for 3 seconds"); TimeUnit.SECONDS.sleep(3); successCount.set(0); final CountDownLatch waitLatch1 = new CountDownLatch(threadCount); final long currentTime = TimeUtil.currentTimeMillis(); final long endTime = currentTime + rule.getDurationInSec() * 1000 - 500; for (int i = 0; i < threadCount; i++) { Thread t = new Thread(() -> { while (TimeUtil.currentTimeMillis() <= endTime) { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount.incrementAndGet(); } try { TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(20)); } catch (InterruptedException e) { e.printStackTrace(); } } waitLatch1.countDown(); }); t.setName("sentinel-simulate-traffic-task-" + i); t.start(); } waitLatch1.await(); assertEquals(threshold, successCount.get()); } @Before public void setUp() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowPartialIntegrationTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.block.flow.param.AbstractTimeBasedTest; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.Collections; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author quguai * @date 2023/10/27 13:44 */ public class ParamFlowPartialIntegrationTest extends AbstractTimeBasedTest { @Before public void setUp() throws Exception { ParamFlowRuleManager.loadRules(new ArrayList<>()); } @After public void tearDown() throws Exception { ParamFlowRuleManager.loadRules(new ArrayList<>()); } @Test public void testParamFlowRegex() { try (MockedStatic mocked = super.mockTimeUtil()) { setCurrentMillis(mocked, 1800000000000L); ParamFlowRule rule = new ParamFlowRule(".*") .setParamIdx(0) .setCount(1); rule.setRegex(true); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); verifyFlow("testParamFlowRegex_1", true, "args"); verifyFlow("testParamFlowRegex_1", true, "args_1"); verifyFlow("testParamFlowRegex_1", false, "args"); verifyFlow("testParamFlowRegex_1", false, "args_1"); verifyFlow("testParamFlowRegex_2", true, "args"); verifyFlow("testParamFlowRegex_2", true, "args_1"); verifyFlow("testParamFlowRegex_2", false, "args"); verifyFlow("testParamFlowRegex_2", false, "args_1"); } } private void verifyFlow(String resource, boolean shouldPass, String... args) { Entry e = null; try { e = SphU.entry(resource, 1, EntryType.IN, args); assertTrue(shouldPass); } catch (BlockException e1) { assertFalse(shouldPass); } finally { if (e != null) { e.exit(1, args); } } } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link ParamFlowRuleManager}. * * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowRuleManagerTest { @Before public void setUp() { ParamFlowRuleManager.loadRules(null); ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() { ParamFlowRuleManager.loadRules(null); ParameterMetricStorage.getMetricsMap().clear(); } @Test public void testLoadParamRulesClearingUnusedMetrics() { final String resA = "resA"; ParamFlowRule ruleA = new ParamFlowRule(resA) .setCount(1) .setParamIdx(0); ParamFlowRuleManager.loadRules(Collections.singletonList(ruleA)); ParameterMetricStorage.getMetricsMap().put(resA, new ParameterMetric()); assertNotNull(ParameterMetricStorage.getParamMetricForResource(resA)); final String resB = "resB"; ParamFlowRule ruleB = new ParamFlowRule(resB) .setCount(2) .setParamIdx(1); ParamFlowRuleManager.loadRules(Collections.singletonList(ruleB)); assertNull("The unused hot param metric should be cleared", ParameterMetricStorage.getParamMetricForResource(resA)); } @Test public void testLoadParamRulesClearingUnusedMetricsForRule() { final String resA = "resA"; ParamFlowRule ruleA1 = new ParamFlowRule(resA) .setCount(1) .setParamIdx(0); ParamFlowRule ruleA2 = new ParamFlowRule(resA) .setCount(2) .setParamIdx(1); ParamFlowRuleManager.loadRules(Arrays.asList(ruleA1, ruleA2)); ParameterMetric metric = new ParameterMetric(); metric.initialize(ruleA1); metric.initialize(ruleA2); ParameterMetricStorage.getMetricsMap().put(resA, metric); ParameterMetric metric1 = ParameterMetricStorage.getParamMetricForResource(resA); assertNotNull(metric1); assertNotNull(metric1.getRuleTimeCounter(ruleA1)); assertNotNull(metric1.getRuleTimeCounter(ruleA2)); ParamFlowRuleManager.loadRules(Arrays.asList(ruleA1)); ParameterMetric metric2 = ParameterMetricStorage.getParamMetricForResource(resA); assertNotNull(metric2); assertNotNull(metric2.getRuleTimeCounter(ruleA1)); assertNull(metric2.getRuleTimeCounter(ruleA2)); } @Test public void testLoadParamRulesAndGet() { final String resA = "abc"; final String resB = "foo"; final String resC = "baz"; // Rule A to C is for resource A. // Rule A is invalid. ParamFlowRule ruleA = new ParamFlowRule(resA).setCount(10); ParamFlowRule ruleB = new ParamFlowRule(resA) .setCount(28) .setParamIdx(1); ParamFlowRule ruleC = new ParamFlowRule(resA) .setCount(8) .setParamIdx(1) .setGrade(RuleConstant.FLOW_GRADE_THREAD); // Rule D is for resource B. ParamFlowRule ruleD = new ParamFlowRule(resB) .setCount(9) .setParamIdx(0) .setParamFlowItemList(Arrays.asList(ParamFlowItem.newItem(7L, 6), ParamFlowItem.newItem(9L, 4))); ParamFlowRuleManager.loadRules(Arrays.asList(ruleA, ruleB, ruleC, ruleD)); // Test for ParamFlowRuleManager#hasRules assertTrue(ParamFlowRuleManager.hasRules(resA)); assertTrue(ParamFlowRuleManager.hasRules(resB)); assertFalse(ParamFlowRuleManager.hasRules(resC)); // Test for ParamFlowRuleManager#getRulesOfResource List rulesForResA = ParamFlowRuleManager.getRulesOfResource(resA); assertEquals(2, rulesForResA.size()); assertFalse(rulesForResA.contains(ruleA)); assertTrue(rulesForResA.contains(ruleB)); assertTrue(rulesForResA.contains(ruleC)); List rulesForResB = ParamFlowRuleManager.getRulesOfResource(resB); assertEquals(1, rulesForResB.size()); assertEquals(ruleD, rulesForResB.get(0)); // Test for ParamFlowRuleManager#getRules List allRules = ParamFlowRuleManager.getRules(); assertFalse(allRules.contains(ruleA)); assertTrue(allRules.contains(ruleB)); assertTrue(allRules.contains(ruleC)); assertTrue(allRules.contains(ruleD)); } @Test public void testLoadParamRulesWithNoMetric() { String resource = "test"; ParamFlowRule paramFlowRule = new ParamFlowRule(resource) .setDurationInSec(1).setParamIdx(1); ParamFlowRuleManager.loadRules(Collections.singletonList(paramFlowRule)); ParamFlowRule newParamFlowRule = new ParamFlowRule(resource) .setDurationInSec(2).setParamIdx(1); ParamFlowRuleManager.loadRules(Collections.singletonList(newParamFlowRule)); List result = ParamFlowRuleManager.getRulesOfResource(resource); assertEquals(1, result.size()); assertEquals(2, result.get(0).getDurationInSec()); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java ================================================ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ParamFlowRuleUtilTest { @Test public void testCheckValidHotParamRule() { // Null or empty resource; ParamFlowRule rule1 = new ParamFlowRule(); ParamFlowRule rule2 = new ParamFlowRule(""); assertFalse(ParamFlowRuleUtil.isValidRule(null)); assertFalse(ParamFlowRuleUtil.isValidRule(rule1)); assertFalse(ParamFlowRuleUtil.isValidRule(rule2)); // Invalid threshold count. ParamFlowRule rule3 = new ParamFlowRule("abc") .setCount(-1) .setParamIdx(1); assertFalse(ParamFlowRuleUtil.isValidRule(rule3)); // Parameter index not set or invalid. ParamFlowRule rule4 = new ParamFlowRule("abc") .setCount(1); ParamFlowRule rule5 = new ParamFlowRule("abc") .setCount(1) .setParamIdx(-1); assertFalse(ParamFlowRuleUtil.isValidRule(rule4)); assertTrue(ParamFlowRuleUtil.isValidRule(rule5)); ParamFlowRule goodRule = new ParamFlowRule("abc") .setCount(10) .setParamIdx(1); assertTrue(ParamFlowRuleUtil.isValidRule(goodRule)); } @Test public void testParseHotParamExceptionItemsFailure() { String valueB = "Sentinel"; Integer valueC = 6; char valueD = 6; float valueE = 11.11f; // Null object will not be parsed. ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName()); // Hot item with empty class type will be treated as string. ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null); ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, ""); // Bad count will not be parsed. ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5); ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName()); List badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE); Map parsedItems = ParamFlowRuleUtil.parseHotItems(badItems); // Value B and E will be parsed, but ignoring the type. assertEquals(2, parsedItems.size()); assertEquals(itemB.getCount(), parsedItems.get(valueB)); assertFalse(parsedItems.containsKey(valueE)); assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE))); } @Test public void testParseHotParamExceptionItemsSuccess() { // Test for empty list. assertEquals(0, ParamFlowRuleUtil.parseHotItems(null).size()); assertEquals(0, ParamFlowRuleUtil.parseHotItems(new ArrayList()).size()); // Test for boxing objects and primitive types. Double valueA = 1.1d; String valueB = "Sentinel"; Integer valueC = 6; char valueD = 'c'; ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1); ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3); ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5); ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD)) .setClassType(char.class.getName()) .setCount(7); List items = Arrays.asList(itemA, itemB, itemC, itemD); Map parsedItems = ParamFlowRuleUtil.parseHotItems(items); assertEquals(itemA.getCount(), parsedItems.get(valueA)); assertEquals(itemB.getCount(), parsedItems.get(valueB)); assertEquals(itemC.getCount(), parsedItems.get(valueC)); assertEquals(itemD.getCount(), parsedItems.get(valueD)); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowSlotTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Collections; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Test cases for {@link ParamFlowSlot}. * * @author Eric Zhao * @since 0.2.0 */ public class ParamFlowSlotTest { private final ParamFlowSlot paramFlowSlot = new ParamFlowSlot(); @Test public void testNegativeParamIdx() throws Throwable { String resourceName = "testNegativeParamIdx"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); ParamFlowRule rule = new ParamFlowRule(resourceName) .setCount(1) .setParamIdx(-1); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi"); assertEquals(2, rule.getParamIdx().longValue()); rule.setParamIdx(-1); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); paramFlowSlot.entry(null, resourceWrapper, null, 1, false, null); // Null args will not trigger conversion. assertEquals(-1, rule.getParamIdx().intValue()); rule.setParamIdx(-100); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi"); assertEquals(100, rule.getParamIdx().longValue()); rule.setParamIdx(0); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi"); assertEquals(0, rule.getParamIdx().longValue()); } @Test public void testEntryWhenParamFlowRuleNotExists() throws Throwable { String resourceName = "testEntryWhenParamFlowRuleNotExists"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc"); // The parameter metric instance will not be created. assertNull(ParameterMetricStorage.getParamMetric(resourceWrapper)); } @Test public void testEntryWhenParamFlowExists() throws Throwable { String resourceName = "testEntryWhenParamFlowExists"; ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); long argToGo = 1L; double count = 1; ParamFlowRule rule = new ParamFlowRule(resourceName) .setCount(count) .setBurstCount(0) .setParamIdx(0); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); ParameterMetric metric = mock(ParameterMetric.class); CacheMap map = new ConcurrentLinkedHashMapWrapper<>(4000); CacheMap> map2 = new ConcurrentLinkedHashMapWrapper<>(4000); when(metric.getRuleTimeCounter(rule)).thenReturn(map); when(metric.getRuleStampedTokenCounter(rule)).thenReturn(map2); map.put(argToGo, new AtomicLong(TimeUtil.currentTimeMillis())); // Insert the mock metric to control pass or block. ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); // The first entry will pass. paramFlowSlot.entry(null, resourceWrapper, null, 1, false, argToGo); // The second entry will be blocked. try { paramFlowSlot.entry(null, resourceWrapper, null, 1, false, argToGo); } catch (ParamFlowException ex) { assertEquals(String.valueOf(argToGo), ex.getMessage()); assertEquals(resourceName, ex.getResourceName()); return; } fail("The second entry should be blocked"); } @Before public void setUp() { ParamFlowRuleManager.loadRules(null); ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() { // Clean the metrics map. ParamFlowRuleManager.loadRules(null); ParameterMetricStorage.getMetricsMap().clear(); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowThrottleRateLimitingCheckerTest.java ================================================ /* * Copyright 1999-2024 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; import com.alibaba.csp.sentinel.util.TimeUtil; import static org.junit.Assert.assertEquals; /** * @author jialiang.linjl */ public class ParamFlowThrottleRateLimitingCheckerTest { @Test public void testSingleValueThrottleCheckQps() throws Exception { final String resourceName = "testSingleValueThrottleCheckQps"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; TimeUtil.currentTimeMillis(); long threshold = 5L; ParamFlowRule rule = new ParamFlowRule(); rule.setResource(resourceName); rule.setCount(threshold); rule.setParamIdx(paramIdx); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); long currentTime = TimeUtil.currentTimeMillis(); long endTime = currentTime + rule.getDurationInSec() * 1000; int successCount = 0; while (currentTime <= endTime - 10) { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount++; } currentTime = TimeUtil.currentTimeMillis(); } assertEquals(successCount, threshold); TimeUnit.SECONDS.sleep(3); currentTime = TimeUtil.currentTimeMillis(); endTime = currentTime + rule.getDurationInSec() * 1000; successCount = 0; while (currentTime <= endTime - 10) { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount++; } currentTime = TimeUtil.currentTimeMillis(); } assertEquals(successCount, threshold); } @Test public void testSingleValueThrottleCheckQpsMultipleThreads() throws Exception { final String resourceName = "testSingleValueThrottleCheckQpsMultipleThreads"; final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); int paramIdx = 0; long threshold = 5L; final ParamFlowRule rule = new ParamFlowRule(resourceName) .setCount(threshold) .setParamIdx(paramIdx) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); final String valueA = "valueA"; ParameterMetric metric = new ParameterMetric(); ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric); metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper(4000)); int threadCount = 40; final CountDownLatch waitLatch = new CountDownLatch(threadCount); final AtomicInteger successCount = new AtomicInteger(); for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount.incrementAndGet(); } waitLatch.countDown(); } }); t.setName("sentinel-simulate-traffic-task-" + i); t.start(); } waitLatch.await(); assertEquals(successCount.get(), 1); successCount.set(0); TimeUnit.SECONDS.sleep(3); successCount.set(0); final CountDownLatch waitLatch1 = new CountDownLatch(threadCount); final long currentTime = TimeUtil.currentTimeMillis(); final long endTime = currentTime + rule.getDurationInSec() * 1000 - 1; for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { long currentTime1 = currentTime; while (currentTime1 <= endTime) { if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) { successCount.incrementAndGet(); } Random random = new Random(); try { TimeUnit.MILLISECONDS.sleep(random.nextInt(20)); } catch (InterruptedException e) { e.printStackTrace(); } currentTime1 = TimeUtil.currentTimeMillis(); } waitLatch1.countDown(); } }); t.setName("sentinel-simulate-traffic-task-" + i); t.start(); } waitLatch1.await(); assertEquals(successCount.get(), threshold); } @Before public void setUp() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() throws Exception { ParameterMetricStorage.getMetricsMap().clear(); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParameterMetricStorageTest.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * @author Eric Zhao */ public class ParameterMetricStorageTest { @Test public void testGetNullParamMetric() { assertNull(ParameterMetricStorage.getParamMetric(null)); } @Test public void testInitParamMetrics() { ParamFlowRule rule = new ParamFlowRule(); rule.setParamIdx(1); int index = 1; String resourceName = "res-" + System.currentTimeMillis(); ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); assertNull(ParameterMetricStorage.getParamMetric(resourceWrapper)); ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule); ParameterMetric metric = ParameterMetricStorage.getParamMetric(resourceWrapper); assertNotNull(metric); assertNotNull(metric.getRuleTimeCounterMap().get(rule)); assertNotNull(metric.getThreadCountMap().get(index)); // Duplicate init. ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule); assertSame(metric, ParameterMetricStorage.getParamMetric(resourceWrapper)); ParamFlowRule rule2 = new ParamFlowRule(); rule2.setParamIdx(1); assertSame(metric, ParameterMetricStorage.getParamMetric(resourceWrapper)); } @Before public void setUp() { ParameterMetricStorage.getMetricsMap().clear(); } @After public void tearDown() { ParameterMetricStorage.getMetricsMap().clear(); } } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParameterMetricTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.block.flow.param; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; /** * Test cases for {@link ParameterMetric}. * * @author Eric Zhao * @since 0.2.0 */ public class ParameterMetricTest { @Test public void testInitAndClearParameterMetric() { // Create a parameter metric for resource "abc". ParameterMetric metric = new ParameterMetric(); ParamFlowRule rule = new ParamFlowRule("abc") .setParamIdx(1); metric.initialize(rule); CacheMap threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx()); assertNotNull(threadCountMap); CacheMap timeRecordMap = metric.getRuleTimeCounter(rule); assertNotNull(timeRecordMap); metric.initialize(rule); assertSame(threadCountMap, metric.getThreadCountMap().get(rule.getParamIdx())); assertSame(timeRecordMap, metric.getRuleTimeCounter(rule)); ParamFlowRule rule2 = new ParamFlowRule("abc") .setParamIdx(1); metric.initialize(rule2); CacheMap timeRecordMap2 = metric.getRuleTimeCounter(rule2); assertSame(timeRecordMap, timeRecordMap2); rule2.setParamIdx(2); metric.initialize(rule2); assertNotSame(timeRecordMap2, metric.getRuleTimeCounter(rule2)); ParamFlowRule rule3 = new ParamFlowRule("abc") .setParamIdx(1) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); metric.initialize(rule3); assertNotSame(timeRecordMap, metric.getRuleTimeCounter(rule3)); metric.clear(); assertEquals(0, metric.getThreadCountMap().size()); assertEquals(0, metric.getRuleTimeCounterMap().size()); assertEquals(0, metric.getRuleTokenCounterMap().size()); } @Test public void testAddAndDecreaseThreadCountCommon() { testAddAndDecreaseThreadCount(PARAM_TYPE_NORMAL); testAddAndDecreaseThreadCount(PARAM_TYPE_ARRAY); testAddAndDecreaseThreadCount(PARAM_TYPE_COLLECTION); } private void testAddAndDecreaseThreadCount(int paramType) { ParamFlowRule rule = new ParamFlowRule(); rule.setParamIdx(0); int n = 3; long[] v = new long[] {19L, 3L, 8L}; ParameterMetric metric = new ParameterMetric(); metric.initialize(rule); assertTrue(metric.getThreadCountMap().containsKey(rule.getParamIdx())); switch (paramType) { case PARAM_TYPE_ARRAY: metric.addThreadCount((Object)v); break; case PARAM_TYPE_COLLECTION: metric.addThreadCount(Arrays.asList(v[0], v[1], v[2])); break; case PARAM_TYPE_NORMAL: default: metric.addThreadCount(v[0]); metric.addThreadCount(v[1]); metric.addThreadCount(v[2]); break; } assertEquals(1, metric.getThreadCountMap().size()); CacheMap threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx()); assertEquals(v.length, threadCountMap.size()); for (long vs : v) { assertEquals(1, threadCountMap.get(vs).get()); } for (int i = 1; i < n; i++) { switch (paramType) { case PARAM_TYPE_ARRAY: metric.addThreadCount((Object)v); break; case PARAM_TYPE_COLLECTION: metric.addThreadCount(Arrays.asList(v[0], v[1], v[2])); break; case PARAM_TYPE_NORMAL: default: metric.addThreadCount(v[0]); metric.addThreadCount(v[1]); metric.addThreadCount(v[2]); break; } } assertEquals(1, metric.getThreadCountMap().size()); threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx()); assertEquals(v.length, threadCountMap.size()); for (long vs : v) { assertEquals(n, threadCountMap.get(vs).get()); } for (int i = 1; i < n; i++) { switch (paramType) { case PARAM_TYPE_ARRAY: metric.decreaseThreadCount((Object)v); break; case PARAM_TYPE_COLLECTION: metric.decreaseThreadCount(Arrays.asList(v[0], v[1], v[2])); break; case PARAM_TYPE_NORMAL: default: metric.decreaseThreadCount(v[0]); metric.decreaseThreadCount(v[1]); metric.decreaseThreadCount(v[2]); break; } } assertEquals(1, metric.getThreadCountMap().size()); threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx()); assertEquals(v.length, threadCountMap.size()); for (long vs : v) { assertEquals(1, threadCountMap.get(vs).get()); } switch (paramType) { case PARAM_TYPE_ARRAY: metric.decreaseThreadCount((Object)v); break; case PARAM_TYPE_COLLECTION: metric.decreaseThreadCount(Arrays.asList(v[0], v[1], v[2])); break; case PARAM_TYPE_NORMAL: default: metric.decreaseThreadCount(v[0]); metric.decreaseThreadCount(v[1]); metric.decreaseThreadCount(v[2]); break; } assertEquals(1, metric.getThreadCountMap().size()); threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx()); assertEquals(0, threadCountMap.size()); } private static final int PARAM_TYPE_NORMAL = 0; private static final int PARAM_TYPE_ARRAY = 1; private static final int PARAM_TYPE_COLLECTION = 2; } ================================================ FILE: sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucketTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.slots.statistic.data; import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; import org.junit.Test; import static org.junit.Assert.*; /** * Test cases for {@link ParamMapBucket}. * * @author Eric Zhao * @since 0.2.0 */ public class ParamMapBucketTest { @Test public void testAddEviction() { ParamMapBucket bucket = new ParamMapBucket(); for (int i = 0; i < ParamMapBucket.DEFAULT_MAX_CAPACITY; i++) { bucket.add(RollingParamEvent.REQUEST_PASSED, 1, "param-" + i); } String lastParam = "param-end"; bucket.add(RollingParamEvent.REQUEST_PASSED, 1, lastParam); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-0")); assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-1")); assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, lastParam)); } @Test public void testAddGetResetCommon() { ParamMapBucket bucket = new ParamMapBucket(); double paramA = 1.1d; double paramB = 2.2d; double paramC = -19.7d; // Block: A 5 | B 1 | C 6 // Pass: A 0 | B 1 | C 7 bucket.add(RollingParamEvent.REQUEST_BLOCKED, 3, paramA); bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramB); bucket.add(RollingParamEvent.REQUEST_BLOCKED, 1, paramB); bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramA); bucket.add(RollingParamEvent.REQUEST_PASSED, 6, paramC); bucket.add(RollingParamEvent.REQUEST_BLOCKED, 4, paramC); bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramC); bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramC); assertEquals(5, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA)); assertEquals(1, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB)); assertEquals(6, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA)); assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB)); assertEquals(7, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC)); bucket.reset(); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB)); assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC)); } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/README.md ================================================ # Sentinel Prometheus Exporter Sentinel Prometheus Exporter is a module which provides the Sentinel metrics data for prometheus. You can integrate it into your Sentinel application, and then get the sentinel metrics in your prometheus. ## How it works when the prometheus server collect the sentinel metrics,it get metrics from sentinel logs ![image](https://github.com/alibaba/Sentinel/assets/71377602/2982209b-a3c7-403b-ae50-1dc7a17f90b7) ## How to use To use Sentinel Prometheus Exporter, you should add the following dependency: ### 1. add sentinel-prometheus-exporter ```xml com.alibaba.csp sentinel-prometheus-metric-exporter x.y.z ``` ### 2. add prometheus dependency ```xml io.prometheus simpleclient 0.3.0 ``` ```xml io.prometheus simpleclient_httpserver 0.3.0 ``` ### 3. set prometheus.yml with fetch config ```yaml scrape_configs: - job_name: 'sentinelMetrics' static_configs: - targets: ['localhost:9092'] ``` ```yaml Note: the port needs to be the same as the value in the configuration (csp.sentinel.prometheus.fetch.port) ``` ## Params for exporter you can set system params to control the exporter behavior ### 1.csp.sentinel.prometheus.fetch.port the port for prometheus exporter,default 9092 ### 2.csp.sentinel.prometheus.fetch.size the max fetch nums for prometheus exporter,in case the memory is not enough,default 1024 ### 3.csp.sentinel.prometheus.fetch.delay the delay time for fetching , may be it is still do some statistics work according to the sliding window size when fetching, so need to set the delay time to insure the accuracy. unit: second default: 0 ### 4.csp.sentinel.prometheus.fetch.identify set the resource which need to fetch,default null,fetch all resources ### 5.csp.sentinel.prometheus.fetch.types the types need to fetch,such as passQps,concurrency format: "xx|xx|xx" default: "passQps|blockQps|exceptionQps|rt|concurrency" you can reset the types as you need to,exm: "passQps|rt|concurrency|occupiedPassQps" the type is same as the MetricNode class variables, with range: {"passQps","blockQps","successQps","exceptionQps","rt","occupiedPassQps","concurrency"} ### 6.csp.sentinel.prometheus.app set the appName when do PromSQL ## how it looks ![image](https://github.com/alibaba/Sentinel/assets/71377602/dedde134-53ed-4b4e-b184-98e55184aacf) ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-parent ${revision} ../../pom.xml sentinel-prometheus-metric-exporter com.alibaba.csp sentinel-core io.prometheus simpleclient 0.3.0 provided io.prometheus simpleclient_httpserver 0.3.0 provided junit junit test ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/prom/MetricConstants.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.prom; /** * The{@link PromExporterInit} the Collector for prometheus exporter. * * @author karl-sy * @date 2023-08-08 09:30 * @since 2.0.0 */ public final class MetricConstants { public static final String METRIC_HELP = "sentinel_metrics"; public static final String RESOURCE = "resource"; public static final String CLASSIFICATION = "classification"; public static final String METRIC_TYPE = "type"; public static final String PASS_QPS = "passQps"; public static final String BLOCK_QPS = "blockQps"; public static final String SUCCESS_QPS = "successQps"; public static final String EXCEPTION_QPS = "exceptionQps"; public static final String RT = "rt"; public static final String OCC_PASS_QPS = "occupiedPassQps"; public static final String CONCURRENCY = "concurrency"; private MetricConstants() { } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/prom/PromExporterInit.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.prom; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.metric.prom.collector.SentinelCollector; import com.alibaba.csp.sentinel.metric.prom.config.PrometheusGlobalConfig; import io.prometheus.client.exporter.HTTPServer; /** * The{@link PromExporterInit} the InitFunc for prometheus exporter. * * @author karl-sy * @date 2023-07-13 21:15 * @since 2.0.0 */ public class PromExporterInit implements InitFunc { @Override public void init() throws Exception { HTTPServer server = null; try { new SentinelCollector().register(); // 开启http服务供prometheus调用 // 默认只提供一个接口 http://ip:port/metrics,返回所有指标 int promPort = PrometheusGlobalConfig.getPromFetchPort(); server = new HTTPServer(promPort); } catch (Throwable e) { RecordLog.warn("[PromExporterInit] failed to init prometheus exporter with exception:", e); } HTTPServer finalServer = server; Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (finalServer != null) { finalServer.stop(); } })); } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/prom/collector/SentinelCollector.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.prom.collector; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.metric.prom.MetricConstants; import com.alibaba.csp.sentinel.metric.prom.config.PrometheusGlobalConfig; import com.alibaba.csp.sentinel.metric.prom.types.GaugeMetricFamily; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.node.metric.MetricSearcher; import com.alibaba.csp.sentinel.node.metric.MetricWriter; import com.alibaba.csp.sentinel.util.PidUtil; import io.prometheus.client.Collector; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * The{@link PromExporterInit} Collector for prometheus exporter. * * @author karl-sy * @date 2023-07-13 21:15 * @since 2.0.0 */ public class SentinelCollector extends Collector { private final Object lock = new Object(); private static final int ONE_SECOND = 1000; private static final String appName = PrometheusGlobalConfig.getPromFetchApp(); private static final String[] types = PrometheusGlobalConfig.getPromFetchTypes(); private static final String identify = PrometheusGlobalConfig.getPromFetchIdentify(); private static final int fetchSize = PrometheusGlobalConfig.getPromFetchSize(); private static final int delayTime = PrometheusGlobalConfig.getPromFetchDelayTime(); private volatile MetricSearcher searcher; private volatile Long lastFetchTime; @Override public List collect() { if (searcher == null) { synchronized (lock) { if (searcher == null) { searcher = new MetricSearcher(MetricWriter.METRIC_BASE_DIR, MetricWriter.formMetricFileName(SentinelConfig.getAppName(), PidUtil.getPid())); } RecordLog.warn("[SentinelCollector] init sentinel metrics searcher with appName:{}", appName); lastFetchTime = System.currentTimeMillis() / ONE_SECOND * ONE_SECOND; } } List list = new ArrayList<>(); long endTime = System.currentTimeMillis() / ONE_SECOND * ONE_SECOND - (long) delayTime * ONE_SECOND; try { List nodes = searcher.findByTimeAndResource(lastFetchTime, endTime, identify); if(nodes == null){ return list; } if(nodes.size() > fetchSize){ nodes = nodes.subList(0,fetchSize); } GaugeMetricFamily metricFamily = new GaugeMetricFamily(appName, MetricConstants.METRIC_HELP, Arrays.asList(MetricConstants.RESOURCE, MetricConstants.CLASSIFICATION, MetricConstants.METRIC_TYPE)); for (MetricNode node : nodes) { long recordTime = node.getTimestamp(); for (String type : types) { double val = getTypeVal(node,type); metricFamily.addMetric(Arrays.asList(node.getResource(), String.valueOf(node.getClassification()),type), val,recordTime); } } list.add(metricFamily); } catch (Exception e) { RecordLog.warn("[SentinelCollector] failed to fetch sentinel metrics with exception:", e); }finally { lastFetchTime = endTime + ONE_SECOND; } return list; } public double getTypeVal(MetricNode node,String type){ if(MetricConstants.PASS_QPS.equals(type)){ return node.getPassQps(); } if(MetricConstants.BLOCK_QPS.equals(type)){ return node.getBlockQps(); } if(MetricConstants.SUCCESS_QPS.equals(type)){ return node.getSuccessQps(); } if(MetricConstants.EXCEPTION_QPS.equals(type)){ return node.getExceptionQps(); } if(MetricConstants.RT.equals(type)){ return node.getRt(); } if(MetricConstants.OCC_PASS_QPS.equals(type)){ return node.getOccupiedPassQps(); } if(MetricConstants.CONCURRENCY.equals(type)){ return node.getConcurrency(); } return -1.0; } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/prom/config/PrometheusGlobalConfig.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.prom.config; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.StringUtil; /** * The config for prometheus exporter. * * @author karl-sy * @date 2023-07-13 21:15 * @since 2.0.0 */ public class PrometheusGlobalConfig { public static final String PROM_FETCH_PORT = "csp.sentinel.prometheus.fetch.port"; public static final String DEFAULT_PROM_FETCH_PORT = "9092"; public static final String PROM_FETCH_SIZE = "csp.sentinel.prometheus.fetch.size"; public static final String DEFAULT_PROM_FETCH_SIZE = "1024"; public static final String PROM_FETCH_DELAY = "csp.sentinel.prometheus.fetch.delay"; public static final String DEFAULT_PROM_FETCH_DELAY = "0"; public static final String PROM_FETCH_IDENTIFY = "csp.sentinel.prometheus.fetch.identify"; public static final String PROM_FETCH_TYPES = "csp.sentinel.prometheus.fetch.types"; public static final String DEFAULT_PROM_FETCH_TYPES = "passQps|blockQps|exceptionQps|rt|concurrency"; public static final String PROM_APP = "csp.sentinel.prometheus.app"; public static final String DEFAULT_PROM_APP = "SENTINEL_APP"; public static int getPromFetchPort() { String config = SentinelConfig.getConfig(PROM_FETCH_PORT); config = StringUtil.isNotBlank(config) ? config : DEFAULT_PROM_FETCH_PORT; return Integer.parseInt(config); } public static int getPromFetchSize() { String config = SentinelConfig.getConfig(PROM_FETCH_SIZE); config = StringUtil.isNotBlank(config) ? config : DEFAULT_PROM_FETCH_SIZE; return Integer.parseInt(config); } public static int getPromFetchDelayTime() { String config = SentinelConfig.getConfig(PROM_FETCH_DELAY); config = StringUtil.isNotBlank(config) ? config : DEFAULT_PROM_FETCH_DELAY; return Integer.parseInt(config); } public static String getPromFetchIdentify() { return SentinelConfig.getConfig(PROM_FETCH_IDENTIFY); } public static String[] getPromFetchTypes() { String config = SentinelConfig.getConfig(PROM_FETCH_TYPES); config = StringUtil.isNotBlank(config) ? config : DEFAULT_PROM_FETCH_TYPES; try { return config.split("\\|"); }catch (Throwable e){ return DEFAULT_PROM_FETCH_TYPES.split("\\|"); } } public static String getPromFetchApp() { String appName = SentinelConfig.getConfig(PROM_APP); if (appName == null) { appName = SentinelConfig.getAppName(); } if (appName == null) { appName = DEFAULT_PROM_APP; } appName = appName.replaceAll("\\.","_"); appName = appName.replaceAll("-","_"); return appName; } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/prom/types/GaugeMetricFamily.java ================================================ /* * Copyright 1999-2021 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.metric.prom.types; import io.prometheus.client.Collector; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * The{@link SentinelCollector} the MetricFamilySamples for prometheus exporter. * * @author karl-sy * @date 2023-07-13 21:15 * @since 2.0.0 */ public class GaugeMetricFamily extends Collector.MetricFamilySamples { private final List labelNames; public GaugeMetricFamily(String name, String help, double value) { super(name, Collector.Type.GAUGE, help, new ArrayList()); labelNames = Collections.emptyList(); samples.add(new Sample( name, labelNames, Collections.emptyList(), value)); } public GaugeMetricFamily(String name, String help, List labelNames) { super(name, Collector.Type.GAUGE, help, new ArrayList()); this.labelNames = labelNames; } public GaugeMetricFamily addMetric(List labelValues, double value, long timestampMs) { if (labelValues.size() != labelNames.size()) { throw new IllegalArgumentException("Incorrect number of labels."); } samples.add(new Sample(name, labelNames, labelValues, value, timestampMs)); return this; } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.metric.prom.PromExporterInit ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/test/java/com/alibaba/csp/sentinel/metric/prom/collector/SentinelCollectorTest.java ================================================ package com.alibaba.csp.sentinel.metric.prom.collector; import com.alibaba.csp.sentinel.node.metric.MetricNode; import org.junit.Assert; import org.junit.Test; public class SentinelCollectorTest { @Test public void testCollector(){ SentinelCollector collector = new SentinelCollector(); MetricNode node = new MetricNode(); node.setPassQps(10); double val = collector.getTypeVal(node,"passQps"); Assert.assertEquals(val, 10,1e-4); } } ================================================ FILE: sentinel-extension/sentinel-prometheus-metric-exporter/src/test/java/com/alibaba/csp/sentinel/metric/prom/types/GaugeMetricFamilyTest.java ================================================ package com.alibaba.csp.sentinel.metric.prom.types; import org.junit.Assert; import org.junit.Test; import java.util.Collections; public class GaugeMetricFamilyTest { @Test public void testGaugeMetricFamily(){ GaugeMetricFamily metricFamily = new GaugeMetricFamily("appName", "sentinel_metrics", Collections.singletonList("type")); metricFamily.addMetric(Collections.singletonList("rt"), 1.0,System.currentTimeMillis()); Assert.assertEquals(metricFamily.samples.get(0).value, 1.0,1e-4); } } ================================================ FILE: sentinel-logging/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-logging pom sentinel-logging-slf4j ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/README.md ================================================ # Sentinel Logging Extension SLF4J To integrate logs of sentinel into your project which uses slf4j for bridge of logging you can simply introduce following dependency to your project: ```xml com.alibaba.csp sentinel-logging-slf4j ${sentinel.version} ``` And if you want to control level of logging special for sentinel the loggers that sentinel uses are called `sentinelRecordLogger` and `sentinelCommandCenterLogger`. For example in XML configration coming with log4j2 implementation: ```xml ``` ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/pom.xml ================================================ com.alibaba.csp sentinel-logging ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-logging-slf4j jar 1.7.25 1.2.0 com.alibaba.csp sentinel-transport-common org.slf4j slf4j-api ${slf4j.version} provided uk.org.lidalia slf4j-test ${slf4j-test.version} test junit junit test org.mockito mockito-core test ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/main/java/com/alibaba/csp/sentinel/logging/slf4j/CommandCenterLogLogger.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.logging.slf4j; import com.alibaba.csp.sentinel.log.LogTarget; import com.alibaba.csp.sentinel.log.Logger; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import org.slf4j.LoggerFactory; /** * @author wavesZh */ @LogTarget(CommandCenterLog.LOGGER_NAME) public class CommandCenterLogLogger implements Logger { private final org.slf4j.Logger logger = LoggerFactory.getLogger(CommandCenterLog.LOGGER_NAME); @Override public void info(String format, Object... arguments) { logger.info(format, arguments); } @Override public void info(String msg, Throwable e) { logger.info(msg, e); } @Override public void warn(String format, Object... arguments) { logger.warn(format, arguments); } @Override public void warn(String msg, Throwable e) { logger.warn(msg, e); } @Override public void trace(String format, Object... arguments) { logger.trace(format, arguments); } @Override public void trace(String msg, Throwable e) { logger.trace(msg, e); } @Override public void debug(String format, Object... arguments) { logger.debug(format, arguments); } @Override public void debug(String msg, Throwable e) { logger.debug(msg, e); } @Override public void error(String format, Object... arguments) { logger.error(format, arguments); } @Override public void error(String msg, Throwable e) { logger.error(msg, e); } } ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/main/java/com/alibaba/csp/sentinel/logging/slf4j/RecordLogLogger.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.logging.slf4j; import com.alibaba.csp.sentinel.log.LogTarget; import com.alibaba.csp.sentinel.log.Logger; import com.alibaba.csp.sentinel.log.RecordLog; import org.slf4j.LoggerFactory; /** * @author wavesZh */ @LogTarget(RecordLog.LOGGER_NAME) public class RecordLogLogger implements Logger { private final org.slf4j.Logger logger = LoggerFactory.getLogger(RecordLog.LOGGER_NAME); @Override public void info(String format, Object... arguments) { logger.info(format, arguments); } @Override public void info(String msg, Throwable e) { logger.info(msg, e); } @Override public void warn(String format, Object... arguments) { logger.warn(format, arguments); } @Override public void warn(String msg, Throwable e) { logger.warn(msg, e); } @Override public void trace(String format, Object... arguments) { logger.trace(format, arguments); } @Override public void trace(String msg, Throwable e) { logger.trace(msg, e); } @Override public void debug(String format, Object... arguments) { logger.debug(format, arguments); } @Override public void debug(String msg, Throwable e) { logger.debug(msg, e); } @Override public void error(String format, Object... arguments) { logger.error(format, arguments); } @Override public void error(String msg, Throwable e) { logger.error(msg, e); } } ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.log.Logger ================================================ com.alibaba.csp.sentinel.logging.slf4j.RecordLogLogger com.alibaba.csp.sentinel.logging.slf4j.CommandCenterLogLogger ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/test/java/com/alibaba/csp/sentinel/logging/slf4j/AbstraceSlf4jLogTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.logging.slf4j; import uk.org.lidalia.slf4jext.Level; import uk.org.lidalia.slf4jtest.TestLoggerFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.*; import java.io.PrintStream; /** * @author xue8 * @author jason */ public abstract class AbstraceSlf4jLogTest { private static final class TestException extends Exception { private static final long serialVersionUID = 1L; } PrintStream mockStream; PrintStream oldOutStream; PrintStream oldErrStream; protected abstract String getLoggerName(); protected abstract void debug(String msg, Object... args); protected abstract void trace(String msg, Object... args); protected abstract void info(String msg, Object... args); protected abstract void warn(String msg, Object... args); protected abstract void error(String msg, Object... args); @Before public void mockOutput() { System.out.println("Try to mock System.out and System.err"); mockStream = mock(PrintStream.class); oldOutStream = System.out; oldOutStream = System.out; System.setOut(mockStream); System.setErr(mockStream); TestLoggerFactory.getInstance().setPrintLevel(Level.TRACE); } @After public void restore() { if (oldOutStream != null) { System.setOut(oldOutStream); System.setErr(oldErrStream); oldOutStream = null; oldErrStream = null; System.out.println("Restore System.out and System.err"); } } @Test public void testRecordLog() { info("init"); verify(mockStream).println(contains("init")); clearInvocations(mockStream); int count = 0; // info test while (count < 1000) { // 0~999 info("Count {}.", count); count ++; } verify(mockStream).println(contains("Count 0.")); verify(mockStream).println(contains("Count 1.")); verify(mockStream).println(contains("Count 99.")); verify(mockStream).println(contains("Count 123.")); verify(mockStream).println(contains("Count 888.")); verify(mockStream).println(contains("Count 999.")); verify(mockStream, times(1000)).println(contains(" INFO ")); verify(mockStream, times(1000)).println(contains(getLoggerName())); clearInvocations(mockStream); // warn test while (count < 2000) { // 1000~1999 warn("Count {}.", count); count ++; } verify(mockStream).println(contains("Count 1000.")); verify(mockStream).println(contains("Count 1223.")); verify(mockStream).println(contains("Count 1888.")); verify(mockStream).println(contains("Count 1999.")); verify(mockStream, times(1000)).println(contains(" WARN ")); verify(mockStream, times(1000)).println(contains(getLoggerName())); clearInvocations(mockStream); // trace test while (count < 3000) { // 2000~2999 trace("Count {}.", count); count ++; } verify(mockStream).println(contains("Count 2000.")); verify(mockStream).println(contains("Count 2999.")); verify(mockStream, times(1000)).println(contains(" TRACE ")); verify(mockStream, times(1000)).println(contains(getLoggerName())); clearInvocations(mockStream); // debug test while (count < 4000) { // 3000~3999 debug("Count {}.", count); count ++; } verify(mockStream).println(contains("Count 3000.")); verify(mockStream).println(contains("Count 3999.")); verify(mockStream, times(1000)).println(contains(" DEBUG ")); verify(mockStream, times(1000)).println(contains(getLoggerName())); clearInvocations(mockStream); // test error while (count < 5000) { // 4000~4999 error("Count {}.", count); count ++; } verify(mockStream).println(contains("Count 4000.")); verify(mockStream).println(contains("Count 4999.")); verify(mockStream, times(1000)).println(contains(" ERROR ")); verify(mockStream, times(1000)).println(contains(getLoggerName())); clearInvocations(mockStream); } @Test public void testLogException() { info("init"); verify(mockStream).println(contains("init")); verify(mockStream, atLeastOnce()).println(contains(getLoggerName())); clearInvocations(mockStream); Exception e = new TestException(); // info test info("some log", e); verify(mockStream).println(contains("INFO")); verify(mockStream).println(isA(TestException.class)); verify(mockStream).println(contains(getLoggerName())); clearInvocations(mockStream); // warn test warn("some log", e); verify(mockStream).println(contains("WARN")); verify(mockStream).println(isA(TestException.class)); verify(mockStream).println(contains(getLoggerName())); clearInvocations(mockStream); // trace test trace("some log", e); verify(mockStream).println(contains("TRACE")); verify(mockStream).println(isA(TestException.class)); verify(mockStream).println(contains(getLoggerName())); clearInvocations(mockStream); // debug test debug("some log", e); verify(mockStream).println(contains("DEBUG")); verify(mockStream).println(isA(TestException.class)); verify(mockStream).println(contains(getLoggerName())); clearInvocations(mockStream); // error test error("some log", e); verify(mockStream).println(contains("ERROR")); verify(mockStream).println(isA(TestException.class)); verify(mockStream).println(contains(getLoggerName())); clearInvocations(mockStream); } } ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/test/java/com/alibaba/csp/sentinel/logging/slf4j/CommandCenterLogTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.logging.slf4j; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; /** * @author jason */ public class CommandCenterLogTest extends AbstraceSlf4jLogTest { @Override protected String getLoggerName() { return CommandCenterLog.LOGGER_NAME; } @Override protected void debug(String msg, Object... args) { CommandCenterLog.debug(msg, args); } @Override protected void trace(String msg, Object... args) { CommandCenterLog.trace(msg, args); } @Override protected void info(String msg, Object... args) { CommandCenterLog.info(msg, args); } @Override protected void warn(String msg, Object... args) { CommandCenterLog.warn(msg, args); } @Override protected void error(String msg, Object... args) { CommandCenterLog.error(msg, args); } } ================================================ FILE: sentinel-logging/sentinel-logging-slf4j/src/test/java/com/alibaba/csp/sentinel/logging/slf4j/RecordLogTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.logging.slf4j; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author jason */ public class RecordLogTest extends AbstraceSlf4jLogTest { @Override protected String getLoggerName() { return RecordLog.LOGGER_NAME; } @Override protected void debug(String msg, Object... args) { RecordLog.debug(msg, args); } @Override protected void trace(String msg, Object... args) { RecordLog.trace(msg, args); } @Override protected void info(String msg, Object... args) { RecordLog.info(msg, args); } @Override protected void warn(String msg, Object... args) { RecordLog.warn(msg, args); } @Override protected void error(String msg, Object... args) { RecordLog.error(msg, args); } } ================================================ FILE: sentinel-transport/README.md ================================================ # Sentinel Transport The Sentinel transport module provides basic interfaces about Sentinel monitoring API server and client (`CommandCenter` and `HeartbeatSender`) as well implementations using different libraries or protocols. ================================================ FILE: sentinel-transport/pom.xml ================================================ 4.0.0 ${project.groupId}:${project.artifactId} pom com.alibaba.csp sentinel-parent ${revision} ../pom.xml sentinel-transport The transport module of Sentinel sentinel-transport-common sentinel-transport-simple-http sentinel-transport-netty-http sentinel-transport-spring-mvc ================================================ FILE: sentinel-transport/sentinel-transport-common/pom.xml ================================================ com.alibaba.csp sentinel-transport ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} jar sentinel-transport-common com.alibaba.csp sentinel-datasource-extension com.alibaba fastjson junit junit test org.mockito mockito-core test ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandCenterProvider.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.CommandCenter; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * Provider for a universal {@link CommandCenter} instance. * * @author cdfive * @since 1.5.0 */ public final class CommandCenterProvider { private static CommandCenter commandCenter = null; static { resolveInstance(); } private static void resolveInstance() { CommandCenter resolveCommandCenter = SpiLoader.of(CommandCenter.class).loadHighestPriorityInstance(); if (resolveCommandCenter == null) { RecordLog.warn("[CommandCenterProvider] WARN: No existing CommandCenter found"); } else { commandCenter = resolveCommandCenter; RecordLog.info("[CommandCenterProvider] CommandCenter resolved: {}", resolveCommandCenter.getClass() .getCanonicalName()); } } /** * Get resolved {@link CommandCenter} instance. * * @return resolved {@code CommandCenter} instance */ public static CommandCenter getCommandCenter() { return commandCenter; } private CommandCenterProvider() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandConstants.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; /** * @author Eric Zhao * @since 1.4.1 */ public final class CommandConstants { public static final String VERSION_COMMAND = "version"; public static final String MSG_INVALID_COMMAND = "Invalid command"; public static final String MSG_UNKNOWN_COMMAND_PREFIX = "Unknown command"; public static final String MSG_SUCCESS = "success"; public static final String MSG_FAIL = "failed"; private CommandConstants() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; /** * Represent a handler that handles a {@link CommandRequest}. * * @author Eric Zhao */ public interface CommandHandler { /** * Handle the given Courier command request. * * @param request the request to handle * @return the response */ CommandResponse handle(CommandRequest request); } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandHandlerInterceptor.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; /** * Intercepts specified command, and can be extended using SPI. * * @author icodening * @since 1.8.4 * @see com.alibaba.csp.sentinel.spi.SpiLoader * @see com.alibaba.csp.sentinel.spi.Spi */ public interface CommandHandlerInterceptor { /** * whether to intercept the specified command * * @param commandName command name, eg. getRules * @return "true" means intercept, "false" means skip */ boolean shouldIntercept(String commandName); /** * intercept the given command request, and return a command response * * @param request commandRequest * @param execution interceptor chain execution * @return command response */ CommandResponse intercept(CommandRequest request, CommandRequestExecution execution); } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandHandlerProvider.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; import java.util.*; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.command.handler.InterceptingCommandHandler; import com.alibaba.csp.sentinel.spi.SpiLoader; import com.alibaba.csp.sentinel.util.StringUtil; /** * Provides and filters command handlers registered via SPI. * * @author Eric Zhao */ public class CommandHandlerProvider implements Iterable { private final SpiLoader spiLoader = SpiLoader.of(CommandHandler.class); /** * Get all command handlers annotated with {@link CommandMapping} with command name. * * @return list of all named command handlers */ public Map namedHandlers() { Map map = new HashMap(); List handlers = spiLoader.loadInstanceList(); List commandHandlerInterceptors = SpiLoader.of(CommandHandlerInterceptor.class).loadInstanceListSorted(); for (CommandHandler handler : handlers) { String name = parseCommandName(handler); if (StringUtil.isEmpty(name)) { continue; } if (!commandHandlerInterceptors.isEmpty()) { List interceptors = new ArrayList<>(); for (CommandHandlerInterceptor commandHandlerInterceptor : commandHandlerInterceptors) { if (commandHandlerInterceptor.shouldIntercept(name)) { interceptors.add(commandHandlerInterceptor); } } if (!interceptors.isEmpty()) { handler = new InterceptingCommandHandler(handler, interceptors); } } map.put(name, handler); } return map; } private String parseCommandName(CommandHandler handler) { CommandMapping commandMapping = handler.getClass().getAnnotation(CommandMapping.class); if (commandMapping != null) { return commandMapping.name(); } else { return null; } } @Override public Iterator iterator() { return spiLoader.loadInstanceList().iterator(); } private static final CommandHandlerProvider INSTANCE = new CommandHandlerProvider(); public static CommandHandlerProvider getInstance() { return INSTANCE; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.util.StringUtil; /** * Command request representation of command center. * * @author Eric Zhao */ public class CommandRequest { private final Map metadata = new HashMap(); private final Map parameters = new HashMap(); private byte[] body; public byte[] getBody() { return body; } public CommandRequest setBody(byte[] body) { this.body = body; return this; } public Map getParameters() { return parameters; } public String getParam(String key) { return parameters.get(key); } public String getParam(String key, String defaultValue) { String value = parameters.get(key); return StringUtil.isBlank(value) ? defaultValue : value; } public CommandRequest addParam(String key, String value) { if (StringUtil.isBlank(key)) { throw new IllegalArgumentException("Parameter key cannot be empty"); } parameters.put(key, value); return this; } public Map getMetadata() { return metadata; } public CommandRequest addMetadata(String key, String value) { if (StringUtil.isBlank(key)) { throw new IllegalArgumentException("Metadata key cannot be empty"); } metadata.put(key, value); return this; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandRequestExecution.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; /** * @author icodening * @since 1.8.4 */ public interface CommandRequestExecution { /** * execute the command request and return the command response. * * @param request command request * @return command response */ CommandResponse execute(CommandRequest request); } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/CommandResponse.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command; /** * Command response representation of command center. * * @param type of the result * @author Eric Zhao */ public class CommandResponse { private final boolean success; private final R result; private final Throwable exception; private CommandResponse(R result) { this(result, true, null); } private CommandResponse(R result, boolean success, Throwable exception) { this.success = success; this.result = result; this.exception = exception; } /** * Construct a successful response with given object. * * @param result result object * @param type of the result * @return constructed server response */ public static CommandResponse ofSuccess(T result) { return new CommandResponse(result); } /** * Construct a failed response with given exception. * * @param ex cause of the failure * @return constructed server response */ public static CommandResponse ofFailure(Throwable ex) { return new CommandResponse(null, false, ex); } /** * Construct a failed response with given exception. * * @param ex cause of the failure * @param result additional message of the failure * @return constructed server response */ public static CommandResponse ofFailure(Throwable ex, T result) { return new CommandResponse(result, false, ex); } public boolean isSuccess() { return success; } public R getResult() { return result; } public Throwable getException() { return exception; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/annotation/CommandMapping.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.annotation; import java.lang.annotation.*; /** * @author Eric Zhao */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface CommandMapping { String name(); /** * Get brief description of the command. * * @return brief description of the command * @since 1.5.0 */ String desc(); } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ApiCommandHandler.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.util.Map; /** *

          * List all available command handlers by request:
          * {@code curl http://ip:commandPort/api} *

          * * @author houyi * @since 1.5.0 */ @CommandMapping(name = "api", desc = "get all available command handlers") public class ApiCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { Map handlers = CommandHandlerProvider.getInstance().namedHandlers(); JSONArray array = new JSONArray(); if (handlers.isEmpty()) { return CommandResponse.ofSuccess(array.toJSONString()); } for (CommandHandler handler : handlers.values()) { CommandMapping commandMapping = handler.getClass().getAnnotation(CommandMapping.class); if (commandMapping == null) { continue; } String api = commandMapping.name(); String desc = commandMapping.desc(); JSONObject obj = new JSONObject(); obj.put("url", "/" + api); obj.put("desc", desc); array.add(obj); } return CommandResponse.ofSuccess(array.toJSONString()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/BasicInfoCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.HostNameUtil; /** * The basic info command returns the runtime properties. * * @author Eric Zhao */ @CommandMapping(name = "basicInfo", desc = "get sentinel config info") public class BasicInfoCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess(HostNameUtil.getConfigString()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchActiveRuleCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.fastjson.JSON; /** * @author jialiang.linjl */ @CommandMapping(name = "getRules", desc = "get all active rules by type, request param: type={ruleType}") public class FetchActiveRuleCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String type = request.getParam("type"); if ("flow".equalsIgnoreCase(type)) { return CommandResponse.ofSuccess(JSON.toJSONString(FlowRuleManager.getRules())); } else if ("degrade".equalsIgnoreCase(type)) { return CommandResponse.ofSuccess(JSON.toJSONString(DegradeRuleManager.getRules())); } else if ("authority".equalsIgnoreCase(type)) { return CommandResponse.ofSuccess(JSON.toJSONString(AuthorityRuleManager.getRules())); } else if ("system".equalsIgnoreCase(type)) { return CommandResponse.ofSuccess(JSON.toJSONString(SystemRuleManager.getRules())); } else { return CommandResponse.ofFailure(new IllegalArgumentException("invalid type")); } } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterNodeByIdCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; /** * @author qinan.qn */ @CommandMapping(name = "clusterNodeById", desc = "get clusterNode VO by id, request param: id={resourceName}") public class FetchClusterNodeByIdCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String id = request.getParam("id"); if (StringUtil.isEmpty(id)) { return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: empty clusterNode name")); } ClusterNode node = ClusterBuilderSlot.getClusterNode(id); if (node != null) { return CommandResponse.ofSuccess(JSON.toJSONString(NodeVo.fromClusterNode(id, node))); } else { return CommandResponse.ofSuccess("{}"); } } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterNodeHumanCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.Map.Entry; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** * @author qinan.qn */ @CommandMapping(name = "cnode", desc = "get clusterNode metrics by id, request param: id={resourceName}") public class FetchClusterNodeHumanCommandHandler implements CommandHandler { private final static String FORMAT = "%-4s%-80s%-10s%-10s%-10s%-11s%-9s%-6s%-10s%-11s%-9s%-11s"; private final static int MAX_LEN = 79; @Override public CommandResponse handle(CommandRequest request) { String name = request.getParam("id"); if (StringUtil.isEmpty(name)) { return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: empty clusterNode name")); } StringBuilder sb = new StringBuilder(); int i = 0; int nameLength = 0; for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { if (e.getKey().getName().contains(name)) { int l = e.getKey().getShowName().length(); if (l > nameLength) { nameLength = l; } if (++i == 30) { break; } } } nameLength = nameLength > MAX_LEN ? MAX_LEN : nameLength; String format = FORMAT.replaceAll("80", String.valueOf(nameLength + 1)); sb.append(String.format(format, "idx", "id", "thread", "pass", "blocked", "success", "total", "aRt", "1m-pass", "1m-block", "1m-all", "exception")).append("\n"); for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { if (e.getKey().getName().contains(name)) { ClusterNode node = e.getValue(); String id = e.getKey().getShowName(); int lenNum = (int)Math.ceil((double)id.length() / nameLength) - 1; sb.append(String.format(format, i + 1, lenNum == 0 ? id : id.substring(0, nameLength), node.curThreadNum(), node.passQps(), node.blockQps(), node.successQps(), node.totalQps(), node.avgRt(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest(), node.exceptionQps())).append("\n"); for (int j = 1; j <= lenNum; ++j) { int start = nameLength * j; int end = j == lenNum ? id.length() : nameLength * (j + 1); sb.append(String.format(format, "", id.substring(start, end), "", "", "", "", "", "", "", "", "", "", "", "")).append("\n"); } if (++i == 30) { break; } } } return CommandResponse.ofSuccess(sb.toString()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchJsonTreeCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.fastjson.JSON; /** * @author leyou */ @CommandMapping(name = "jsonTree", desc = "get tree node VO start from root node") public class FetchJsonTreeCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { List results = new ArrayList(); visit(Constants.ROOT, results, null); return CommandResponse.ofSuccess(JSON.toJSONString(results)); } /** * Preorder traversal. */ private void visit(DefaultNode node, List results, String parentId) { NodeVo vo = NodeVo.fromDefaultNode(node, parentId); results.add(vo); String id = vo.getId(); for (Node n : node.getChildList()) { visit((DefaultNode)n, results, id); } } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchOriginCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.Map.Entry; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** * @author qinan.qn */ @CommandMapping(name = "origin", desc = "get origin clusterNode by id, request param: id={resourceName}") public class FetchOriginCommandHandler implements CommandHandler { private final static String FORMAT = "%-4s%-80s%-10s%-10s%-11s%-9s%-6s%-10s%-11s%-9s"; private final static int MAX_LEN = 79; @Override public CommandResponse handle(CommandRequest request) { StringBuilder sb = new StringBuilder(); String name = request.getParam("id"); ClusterNode cNode = null; boolean exactly = false; for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { if (e.getKey().getName().equals(name)) { cNode = e.getValue(); sb.append("id: ").append(e.getKey().getShowName()).append("\n"); sb.append("\n"); exactly = true; break; } } if (!exactly) { for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { if (e.getKey().getName().indexOf(name) > 0) { cNode = e.getValue(); sb.append("id: ").append(e.getKey().getShowName()).append("\n"); sb.append("\n"); break; } } } if (cNode == null) { return CommandResponse.ofSuccess("Not find cNode with id " + name); } int i = 0; int nameLength = 0; for (Entry e : cNode.getOriginCountMap().entrySet()) { int l = e.getKey().length(); if (l > nameLength) { nameLength = l; } if (++i == 120) { break; } } nameLength = nameLength > MAX_LEN ? MAX_LEN : nameLength; String format = FORMAT.replaceAll("80", String.valueOf(nameLength + 1)); i = 0; sb.append(String .format(format, "idx", "origin", "threadNum", "passQps", "blockQps", "totalQps", "aRt", "1m-pass", "1m-block", "1m-total")).append("\n"); for (Entry e : cNode.getOriginCountMap().entrySet()) { StatisticNode node = e.getValue(); String id = e.getKey(); int lenNum = (int)Math.ceil((double)id.length() / nameLength) - 1; sb.append(String .format(format, i + 1, lenNum == 0 ? id : id.substring(0, nameLength), node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())) .append("\n"); for (int j = 1; j <= lenNum; ++j) { int start = nameLength * j; int end = j == lenNum ? id.length() : nameLength * (j + 1); sb.append(String .format(format, "", id.substring(start, end), "", "", "", "", "", "", "", "", "", "", "", "")) .append("\n"); } if (++i == 30) { break; } } return CommandResponse.ofSuccess(sb.toString()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchSimpleClusterNodeCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.fastjson.JSONArray; /** * @author jialiang.linjl */ @CommandMapping(name = "clusterNode", desc = "get all clusterNode VO, use type=notZero to ignore those nodes with totalRequest <=0") public class FetchSimpleClusterNodeCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { /* * type==notZero means nodes whose totalRequest <= 0 will be ignored. */ String type = request.getParam("type"); List list = new ArrayList(); Map map = ClusterBuilderSlot.getClusterNodeMap(); if (map == null) { return CommandResponse.ofSuccess(JSONArray.toJSONString(list)); } for (Map.Entry entry : map.entrySet()) { if ("notZero".equalsIgnoreCase(type)) { if (entry.getValue().totalRequest() > 0) { list.add(NodeVo.fromClusterNode(entry.getKey(), entry.getValue())); } } else { list.add(NodeVo.fromClusterNode(entry.getKey(), entry.getValue())); } } return CommandResponse.ofSuccess(JSONArray.toJSONString(list)); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchSystemStatusCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.fastjson.JSONObject; /** * @author jialiang.linjl */ @CommandMapping(name = "systemStatus", desc = "get system status") public class FetchSystemStatusCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { Map systemStatus = new HashMap(); systemStatus.put("rqps", Constants.ENTRY_NODE.successQps()); systemStatus.put("qps", Constants.ENTRY_NODE.passQps()); systemStatus.put("b", Constants.ENTRY_NODE.blockQps()); systemStatus.put("r", Constants.ENTRY_NODE.avgRt()); systemStatus.put("t", Constants.ENTRY_NODE.curThreadNum()); return CommandResponse.ofSuccess(JSONObject.toJSONString(systemStatus)); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchTreeCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.node.Node; /** * @author qinan.qn */ @CommandMapping(name = "tree", desc = "get metrics in tree mode, use id to specify detailed tree root") public class FetchTreeCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String id = request.getParam("id"); StringBuilder sb = new StringBuilder(); DefaultNode start = Constants.ROOT; if (id == null) { visitTree(0, start, sb); } else { boolean exactly = false; for (Node n : start.getChildList()) { DefaultNode dn = (DefaultNode)n; if (dn.getId().getName().equals(id)) { visitTree(0, dn, sb); exactly = true; break; } } if (!exactly) { for (Node n : start.getChildList()) { DefaultNode dn = (DefaultNode)n; if (dn.getId().getName().contains(id)) { visitTree(0, dn, sb); } } } } sb.append("\r\n\r\n"); sb.append( "t:threadNum pq:passQps bq:blockQps tq:totalQps rt:averageRt prq: passRequestQps 1mp:1m-pass " + "1mb:1m-block 1mt:1m-total").append("\r\n"); return CommandResponse.ofSuccess(sb.toString()); } private void visitTree(int level, DefaultNode node, /*@NonNull*/ StringBuilder sb) { for (int i = 0; i < level; ++i) { sb.append("-"); } if (!(node instanceof EntranceNode)) { sb.append(String.format("%s(t:%s pq:%s bq:%s tq:%s rt:%s prq:%s 1mp:%s 1mb:%s 1mt:%s)", node.getId().getShowName(), node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), node.successQps(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())).append("\n"); } else { sb.append(String.format("EntranceNode: %s(t:%s pq:%s bq:%s tq:%s rt:%s prq:%s 1mp:%s 1mb:%s 1mt:%s)", node.getId().getShowName(), node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), node.successQps(), node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())).append("\n"); } for (Node n : node.getChildList()) { DefaultNode dn = (DefaultNode)n; visitTree(level + 1, dn, sb); } } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/InterceptingCommandHandler.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.*; import com.alibaba.csp.sentinel.util.AssertUtil; import java.util.Iterator; import java.util.List; import java.util.function.Function; /** * intercept specified command handler * * @author icodening * @date 2022.03.03 */ public class InterceptingCommandHandler implements CommandHandler { private final CommandHandler delegate; private final List> commandHandlerInterceptors; public InterceptingCommandHandler(CommandHandler delegate, List> commandHandlerInterceptors) { AssertUtil.notNull(delegate, "delegate cannot be null"); AssertUtil.notNull(commandHandlerInterceptors, "commandHandlerInterceptors cannot be null"); this.delegate = delegate; this.commandHandlerInterceptors = commandHandlerInterceptors; } @Override public CommandResponse handle(CommandRequest request) { return new InterceptingRequestExecution<>(commandHandlerInterceptors.iterator(), delegate::handle).execute(request); } private static class InterceptingRequestExecution implements CommandRequestExecution { private final Iterator> iterator; private final Function> commandResponseFunction; public InterceptingRequestExecution(Iterator> iterator, Function> commandResponseFunction) { this.iterator = iterator; this.commandResponseFunction = commandResponseFunction; } @Override public CommandResponse execute(CommandRequest request) { if (this.iterator.hasNext()) { CommandHandlerInterceptor nextInterceptor = this.iterator.next(); return nextInterceptor.intercept(request, this); } return commandResponseFunction.apply(request); } } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyRulesCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.net.URLDecoder; import java.util.List; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.VersionUtil; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import static com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry.*; /** * @author jialiang.linjl * @author Eric Zhao */ @CommandMapping(name = "setRules", desc = "modify the rules, accept param: type={ruleType}&data={ruleJson}") public class ModifyRulesCommandHandler implements CommandHandler { private static final int FASTJSON_MINIMAL_VER = 0x01020C00; @Override public CommandResponse handle(CommandRequest request) { // XXX from 1.7.2, force to fail when fastjson is older than 1.2.12 // We may need a better solution on this. if (VersionUtil.fromVersionString(JSON.VERSION) < FASTJSON_MINIMAL_VER) { // fastjson too old return CommandResponse.ofFailure(new RuntimeException("The \"fastjson-" + JSON.VERSION + "\" introduced in application is too old, you need fastjson-1.2.12 at least.")); } String type = request.getParam("type"); // rule data in get parameter String data = request.getParam("data"); if (StringUtil.isNotEmpty(data)) { try { data = URLDecoder.decode(data, "utf-8"); } catch (Exception e) { RecordLog.info("Decode rule data error", e); return CommandResponse.ofFailure(e, "decode rule data error"); } } RecordLog.info("Receiving rule change (type: {}): {}", type, data); String result = "success"; if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) { List flowRules = JSONArray.parseArray(data, FlowRule.class); FlowRuleManager.loadRules(flowRules); if (!writeToDataSource(getFlowDataSource(), flowRules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } else if (AUTHORITY_RULE_TYPE.equalsIgnoreCase(type)) { List rules = JSONArray.parseArray(data, AuthorityRule.class); AuthorityRuleManager.loadRules(rules); if (!writeToDataSource(getAuthorityDataSource(), rules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } else if (DEGRADE_RULE_TYPE.equalsIgnoreCase(type)) { List rules = JSONArray.parseArray(data, DegradeRule.class); DegradeRuleManager.loadRules(rules); if (!writeToDataSource(getDegradeDataSource(), rules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } else if (SYSTEM_RULE_TYPE.equalsIgnoreCase(type)) { List rules = JSONArray.parseArray(data, SystemRule.class); SystemRuleManager.loadRules(rules); if (!writeToDataSource(getSystemSource(), rules)) { result = WRITE_DS_FAILURE_MSG; } return CommandResponse.ofSuccess(result); } return CommandResponse.ofFailure(new IllegalArgumentException("invalid type")); } /** * Write target value to given data source. * * @param dataSource writable data source * @param value target value to save * @param value type * @return true if write successful or data source is empty; false if error occurs */ private boolean writeToDataSource(WritableDataSource dataSource, T value) { if (dataSource != null) { try { dataSource.write(value); } catch (Exception e) { RecordLog.warn("Write data source failed", e); return false; } } return true; } private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)"; private static final String FLOW_RULE_TYPE = "flow"; private static final String DEGRADE_RULE_TYPE = "degrade"; private static final String SYSTEM_RULE_TYPE = "system"; private static final String AUTHORITY_RULE_TYPE = "authority"; } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/OnOffGetCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; /** * @author youji.zj */ @CommandMapping(name = "getSwitch", desc = "get sentinel switch status") public class OnOffGetCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess("Sentinel switch value: " + Constants.ON); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/OnOffSetCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.Constants; /** * @author youji.zj */ @CommandMapping(name = "setSwitch", desc = "set sentinel switch, accept param: value={true|false}") public class OnOffSetCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { String value = request.getParam("value"); try { Constants.ON = Boolean.valueOf(value); } catch (Exception e) { RecordLog.info("Bad value when setting global switch", e); } String info = "Sentinel set switch value: " + value; RecordLog.info(info); return CommandResponse.ofSuccess(info); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/SendMetricCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.node.metric.MetricNode; import com.alibaba.csp.sentinel.node.metric.MetricSearcher; import com.alibaba.csp.sentinel.node.metric.MetricWriter; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.util.PidUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Retrieve and aggregate {@link MetricNode} metrics. * * @author leyou * @author Eric Zhao */ @CommandMapping(name = "metric", desc = "get and aggregate metrics, accept param: " + "startTime={startTime}&endTime={endTime}&maxLines={maxLines}&identify={resourceName}") public class SendMetricCommandHandler implements CommandHandler { private volatile MetricSearcher searcher; private final Object lock = new Object(); @Override public CommandResponse handle(CommandRequest request) { // Note: not thread-safe. if (searcher == null) { synchronized (lock) { String appName = SentinelConfig.getAppName(); if (appName == null) { appName = ""; } if (searcher == null) { searcher = new MetricSearcher(MetricWriter.METRIC_BASE_DIR, MetricWriter.formMetricFileName(appName, PidUtil.getPid())); } } } String startTimeStr = request.getParam("startTime"); String endTimeStr = request.getParam("endTime"); String maxLinesStr = request.getParam("maxLines"); String identity = request.getParam("identity"); long startTime = -1; int maxLines = 6000; if (StringUtil.isNotBlank(startTimeStr)) { startTime = Long.parseLong(startTimeStr); } else { return CommandResponse.ofSuccess(""); } List list; try { // Find by end time if set. if (StringUtil.isNotBlank(endTimeStr)) { long endTime = Long.parseLong(endTimeStr); list = searcher.findByTimeAndResource(startTime, endTime, identity); } else { if (StringUtil.isNotBlank(maxLinesStr)) { maxLines = Integer.parseInt(maxLinesStr); } maxLines = Math.min(maxLines, 12000); list = searcher.find(startTime, maxLines); } } catch (Exception ex) { return CommandResponse.ofFailure(new RuntimeException("Error when retrieving metrics", ex)); } if (list == null) { list = new ArrayList<>(); } if (StringUtil.isBlank(identity)) { addCpuUsageAndLoad(list); } StringBuilder sb = new StringBuilder(); for (MetricNode node : list) { sb.append(node.toThinString()).append("\n"); } return CommandResponse.ofSuccess(sb.toString()); } /** * add current cpu usage and load to the metric list. * * @param list metric list, should not be null */ private void addCpuUsageAndLoad(List list) { long time = TimeUtil.currentTimeMillis() / 1000 * 1000; double load = SystemRuleManager.getCurrentSystemAvgLoad(); double usage = SystemRuleManager.getCurrentCpuUsage(); if (load > 0) { MetricNode loadNode = toNode(load, time, Constants.SYSTEM_LOAD_RESOURCE_NAME); list.add(loadNode); } if (usage > 0) { MetricNode usageNode = toNode(usage, time, Constants.CPU_USAGE_RESOURCE_NAME); list.add(usageNode); } } /** * transfer the value to a MetricNode, the value will multiply 10000 then truncate * to long value, and as the {@link MetricNode#passQps}. *

          * This is an eclectic scheme before we have a standard metric format. *

          * * @param value value to save. * @param ts timestamp * @param resource resource name. * @return a MetricNode represents the value. */ private MetricNode toNode(double value, long ts, String resource) { MetricNode node = new MetricNode(); node.setPassQps((long)(value * 10000)); node.setTimestamp(ts); node.setResource(resource); return node; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/VersionCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; /** * @author jialiang.linjl * @author Eric Zhao */ @CommandMapping(name = "version", desc = "get sentinel version") public class VersionCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess(Constants.SENTINEL_VERSION); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler.cluster; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.fastjson.JSONObject; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "getClusterMode", desc = "get cluster mode status") public class FetchClusterModeCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { JSONObject res = new JSONObject() .fluentPut("mode", ClusterStateManager.getMode()) .fluentPut("lastModified", ClusterStateManager.getLastModified()) .fluentPut("clientAvailable", isClusterClientSpiAvailable()) .fluentPut("serverAvailable", isClusterServerSpiAvailable()); return CommandResponse.ofSuccess(res.toJSONString()); } private boolean isClusterClientSpiAvailable() { return TokenClientProvider.getClient() != null; } private boolean isClusterServerSpiAvailable() { return EmbeddedClusterTokenServerProvider.getServer() != null; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.handler.cluster; import com.alibaba.csp.sentinel.cluster.ClusterStateManager; import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author Eric Zhao * @since 1.4.0 */ @CommandMapping(name = "setClusterMode", desc = "set cluster mode, accept param: mode={0|1} 0:client mode 1:server mode") public class ModifyClusterModeCommandHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { try { int mode = Integer.valueOf(request.getParam("mode")); if (mode == ClusterStateManager.CLUSTER_CLIENT && !TokenClientProvider.isClientSpiAvailable()) { return CommandResponse.ofFailure(new IllegalStateException("token client mode not available: no SPI found")); } if (mode == ClusterStateManager.CLUSTER_SERVER && !isClusterServerSpiAvailable()) { return CommandResponse.ofFailure(new IllegalStateException("token server mode not available: no SPI found")); } RecordLog.info("[ModifyClusterModeCommandHandler] Modifying cluster mode to: {}", mode); ClusterStateManager.applyState(mode); return CommandResponse.ofSuccess("success"); } catch (NumberFormatException ex) { return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter")); } catch (Exception ex) { return CommandResponse.ofFailure(ex); } } private boolean isClusterServerSpiAvailable() { return EmbeddedClusterTokenServerProvider.isServerSpiAvailable(); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/vo/NodeVo.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.command.vo; import java.util.UUID; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; /** * This class is view object of {@link DefaultNode} or {@link ClusterNode}. * * @author leyou */ public class NodeVo { private String id; private String parentId; private String resource; private Integer threadNum; private Long passQps; private Long blockQps; private Long totalQps; private Long averageRt; private Long successQps; private Long exceptionQps; private Long oneMinutePass; private Long oneMinuteBlock; private Long oneMinuteException; private Long oneMinuteTotal; private Long timestamp; /** * {@link DefaultNode} holds statistics of every node in the invoke tree. * We use parentId to hold the tree structure. * * @param node the DefaultNode to be presented. * @param parentId random generated parent node id, may be a random UUID * @return node view object. */ public static NodeVo fromDefaultNode(DefaultNode node, String parentId) { if (node == null) { return null; } NodeVo vo = new NodeVo(); vo.id = UUID.randomUUID().toString(); vo.parentId = parentId; vo.resource = node.getId().getShowName(); vo.threadNum = node.curThreadNum(); vo.passQps = (long) node.passQps(); vo.blockQps = (long) node.blockQps(); vo.totalQps = (long) node.totalQps(); vo.averageRt = (long) node.avgRt(); vo.successQps = (long) node.successQps(); vo.exceptionQps = (long) node.exceptionQps(); vo.oneMinuteException = node.totalException(); vo.oneMinutePass = node.totalRequest() - node.blockRequest(); vo.oneMinuteBlock = node.blockRequest(); vo.oneMinuteTotal = node.totalRequest(); vo.timestamp = System.currentTimeMillis(); return vo; } /** * {@link ClusterNode} holds total statistics of the same resource name. * * @param name resource name. * @param node the ClusterNode to be presented. * @return node view object. */ public static NodeVo fromClusterNode(ResourceWrapper name, ClusterNode node) { return fromClusterNode(name.getShowName(), node); } /** * {@link ClusterNode} holds total statistics of the same resource name. * * @param name resource name. * @param node the ClusterNode to be presented. * @return node view object. */ public static NodeVo fromClusterNode(String name, ClusterNode node) { if (node == null) { return null; } NodeVo vo = new NodeVo(); vo.resource = name; vo.threadNum = node.curThreadNum(); vo.passQps = (long) node.passQps(); vo.blockQps = (long) node.blockQps(); vo.totalQps = (long) node.totalQps(); vo.averageRt = (long) node.avgRt(); vo.successQps = (long) node.successQps(); vo.exceptionQps = (long) node.exceptionQps(); vo.oneMinuteException = node.totalException(); vo.oneMinutePass = node.totalRequest() - node.blockRequest(); vo.oneMinuteBlock = node.blockRequest(); vo.oneMinuteTotal = node.totalRequest(); vo.timestamp = System.currentTimeMillis(); return vo; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Integer getThreadNum() { return threadNum; } public void setThreadNum(Integer threadNum) { this.threadNum = threadNum; } public Long getPassQps() { return passQps; } public void setPassQps(Long passQps) { this.passQps = passQps; } public Long getBlockQps() { return blockQps; } public void setBlockQps(Long blockQps) { this.blockQps = blockQps; } public Long getTotalQps() { return totalQps; } public void setTotalQps(Long totalQps) { this.totalQps = totalQps; } public Long getAverageRt() { return averageRt; } public void setAverageRt(Long averageRt) { this.averageRt = averageRt; } public Long getSuccessQps() { return successQps; } public void setSuccessQps(Long successQps) { this.successQps = successQps; } public Long getExceptionQps() { return exceptionQps; } public void setExceptionQps(Long exceptionQps) { this.exceptionQps = exceptionQps; } public Long getOneMinuteException() { return oneMinuteException; } public void setOneMinuteException(Long oneMinuteException) { this.oneMinuteException = oneMinuteException; } public Long getOneMinutePass() { return oneMinutePass; } public void setOneMinutePass(Long oneMinutePass) { this.oneMinutePass = oneMinutePass; } public Long getOneMinuteBlock() { return oneMinuteBlock; } public void setOneMinuteBlock(Long oneMinuteBlock) { this.oneMinuteBlock = oneMinuteBlock; } public Long getOneMinuteTotal() { return oneMinuteTotal; } public void setOneMinuteTotal(Long oneMinuteTotal) { this.oneMinuteTotal = oneMinuteTotal; } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/heartbeat/HeartbeatSenderProvider.java ================================================ /* * Copyright 1999-2019 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.heartbeat; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.spi.SpiLoader; /** * @author Eric Zhao * @since 1.6.0 */ public final class HeartbeatSenderProvider { private static HeartbeatSender heartbeatSender = null; static { resolveInstance(); } private static void resolveInstance() { HeartbeatSender resolved = SpiLoader.of(HeartbeatSender.class).loadHighestPriorityInstance(); if (resolved == null) { RecordLog.warn("[HeartbeatSenderProvider] WARN: No existing HeartbeatSender found"); } else { heartbeatSender = resolved; RecordLog.info("[HeartbeatSenderProvider] HeartbeatSender activated: {}", resolved.getClass() .getCanonicalName()); } } /** * Get resolved {@link HeartbeatSender} instance. * * @return resolved {@code HeartbeatSender} instance */ public static HeartbeatSender getHeartbeatSender() { return heartbeatSender; } private HeartbeatSenderProvider() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/CommandCenter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport; /** * @author Eric Zhao */ public interface CommandCenter { /** * Prepare and init for the command center (e.g. register commands). * This will be executed before starting. * * @throws Exception if error occurs */ void beforeStart() throws Exception; /** * Start the command center in the background. * This method should NOT block. * * @throws Exception if error occurs */ void start() throws Exception; /** * Stop the command center and do cleanup. * * @throws Exception if error occurs */ void stop() throws Exception; } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/HeartbeatSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport; /** * The heartbeat sender which is responsible for sending heartbeat to remote dashboard * periodically per {@code interval}. * * @author leyou * @author Eric Zhao */ public interface HeartbeatSender { /** * Send heartbeat to Sentinel Dashboard. Each invocation of this method will send * heartbeat once. Sentinel core is responsible for invoking this method * at every {@link #intervalMs()} interval. * * @return whether heartbeat is successfully send. * @throws Exception if error occurs */ boolean sendHeartbeat() throws Exception; /** * Default interval in milliseconds of the sender. It would take effect only when * the heartbeat interval is not configured in Sentinel config property. * * @return default interval of the sender in milliseconds */ long intervalMs(); } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/client/CommandClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.client; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; /** * Basic interface for clients that sending commands. * * @author Eric Zhao */ public interface CommandClient { /** * Send a command to target destination. * * @param host target host * @param port target port * @param request command request * @return the response from target command server * @throws Exception when unexpected error occurs */ CommandResponse sendCommand(String host, int port, CommandRequest request) throws Exception; } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/config/TransportConfig.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.config; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import java.util.ArrayList; import java.util.List; /** * @author Carpenter Lee * @author Jason Joo * @author Leo Li */ public class TransportConfig { public static final String CONSOLE_SERVER = "csp.sentinel.dashboard.server"; public static final String SERVER_PORT = "csp.sentinel.api.port"; public static final String HEARTBEAT_INTERVAL_MS = "csp.sentinel.heartbeat.interval.ms"; public static final String HEARTBEAT_CLIENT_IP = "csp.sentinel.heartbeat.client.ip"; public static final String HEARTBEAT_API_PATH = "csp.sentinel.heartbeat.api.path"; public static final String HEARTBEAT_DEFAULT_PATH = "/registry/machine"; private static int runtimePort = -1; /** * Get heartbeat interval in milliseconds. * * @return heartbeat interval in milliseconds if exists, or null if not configured or invalid config */ public static Long getHeartbeatIntervalMs() { String interval = SentinelConfig.getConfig(HEARTBEAT_INTERVAL_MS); try { return interval == null ? null : Long.parseLong(interval); } catch (Exception ex) { RecordLog.warn("[TransportConfig] Failed to parse heartbeat interval: " + interval); return null; } } /** * Get a list of Endpoint(protocol, ip/domain, port) indicating Sentinel Dashboard's address.
          * NOTE: only support HTTP and HTTPS protocol * * @return list of Endpoint(protocol, ip/domain, port).
          * May not be null.
          * An empty list returned when not configured. */ public static List getConsoleServerList() { String config = SentinelConfig.getConfig(CONSOLE_SERVER); List list = new ArrayList(); if (StringUtil.isBlank(config)) { return list; } int pos = -1; int cur = 0; while (true) { pos = config.indexOf(',', cur); if (cur < config.length() - 1 && pos < 0) { // for single segment, pos move to the end pos = config.length(); } if (pos < 0) { break; } if (pos <= cur) { cur ++; continue; } // parsing String ipPortStr = config.substring(cur, pos); cur = pos + 1; if (StringUtil.isBlank(ipPortStr)) { continue; } ipPortStr = ipPortStr.trim(); int port = 80; Protocol protocol = Protocol.HTTP; if (ipPortStr.startsWith("http://")) { ipPortStr = ipPortStr.substring(7); } else if (ipPortStr.startsWith("https://")) { ipPortStr = ipPortStr.substring(8); port = 443; protocol = Protocol.HTTPS; } int index = ipPortStr.indexOf(":"); if (index == 0) { // skip continue; } String host = ipPortStr; if (index >= 0) { try { port = Integer.parseInt(ipPortStr.substring(index + 1)); if (port <= 1 || port >= 65535) { throw new RuntimeException("Port number [" + port + "] over range"); } } catch (Exception e) { RecordLog.warn("Parse port of dashboard server failed: " + ipPortStr, e); // skip continue; } host = ipPortStr.substring(0, index); } list.add(new Endpoint(protocol, host, port)); } return list; } public static int getRuntimePort() { return runtimePort; } /** * Get Server port of this HTTP server. * * @return the port, maybe null if not configured. */ public static String getPort() { if (runtimePort > 0) { return String.valueOf(runtimePort); } return SentinelConfig.getConfig(SERVER_PORT, true); } /** * Set real port this HTTP server uses. * * @param port real port. */ public static void setRuntimePort(int port) { runtimePort = port; } /** * Get heartbeat client local ip. * If the client ip not configured,it will be the address of local host * * @return the local ip. */ public static String getHeartbeatClientIp() { String ip = SentinelConfig.getConfig(HEARTBEAT_CLIENT_IP, true); if (StringUtil.isBlank(ip)) { ip = HostNameUtil.getIp(); } return ip; } /** * Get the heartbeat api path. If the machine registry path of the dashboard * is modified, then the API path should also be consistent with the API path of the dashboard. * * @return the heartbeat api path * @since 1.7.1 */ public static String getHeartbeatApiPath() { String apiPath = SentinelConfig.getConfig(HEARTBEAT_API_PATH); if (StringUtil.isBlank(apiPath)) { return HEARTBEAT_DEFAULT_PATH; } if (!apiPath.startsWith("/")) { apiPath = "/" + apiPath; } return apiPath; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/endpoint/Endpoint.java ================================================ package com.alibaba.csp.sentinel.transport.endpoint; import java.net.InetSocketAddress; /** * @author Leo Li */ public class Endpoint { private Protocol protocol; private String host; private int port; public Endpoint(Protocol protocol, String host, int port) { this.protocol = protocol; this.host = host; this.port = port; } public Protocol getProtocol() { return protocol; } public void setProtocol(Protocol protocol) { this.protocol = protocol; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } @Override public String toString() { return "Endpoint{" + "protocol=" + protocol + ", host='" + host + '\'' + ", port=" + port + '}'; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/endpoint/Protocol.java ================================================ package com.alibaba.csp.sentinel.transport.endpoint; /** * @author Leo Li * @author Yanming Zhou */ public enum Protocol { HTTP, HTTPS; public String getProtocol() { return name().toLowerCase(); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/init/CommandCenterInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.init; import com.alibaba.csp.sentinel.command.CommandCenterProvider; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.init.InitOrder; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.CommandCenter; /** * @author Eric Zhao */ @InitOrder(-1) public class CommandCenterInitFunc implements InitFunc { @Override public void init() throws Exception { CommandCenter commandCenter = CommandCenterProvider.getCommandCenter(); if (commandCenter == null) { RecordLog.warn("[CommandCenterInitFunc] Cannot resolve CommandCenter"); return; } commandCenter.beforeStart(); commandCenter.start(); RecordLog.info("[CommandCenterInit] Starting command center: " + commandCenter.getClass().getCanonicalName()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/init/HeartbeatSenderInitFunc.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.init; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.init.InitOrder; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; /** * Global init function for heartbeat sender. * * @author Eric Zhao */ @InitOrder(-1) public class HeartbeatSenderInitFunc implements InitFunc { private ScheduledExecutorService pool = null; private void initSchedulerIfNeeded() { if (pool == null) { pool = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("sentinel-heartbeat-send-task", true), new DiscardOldestPolicy()); } } @Override public void init() { HeartbeatSender sender = HeartbeatSenderProvider.getHeartbeatSender(); if (sender == null) { RecordLog.warn("[HeartbeatSenderInitFunc] WARN: No HeartbeatSender loaded"); return; } initSchedulerIfNeeded(); long interval = retrieveInterval(sender); setIntervalIfNotExists(interval); scheduleHeartbeatTask(sender, interval); } private boolean isValidHeartbeatInterval(Long interval) { return interval != null && interval > 0; } private void setIntervalIfNotExists(long interval) { SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(interval)); } long retrieveInterval(/*@NonNull*/ HeartbeatSender sender) { Long intervalInConfig = TransportConfig.getHeartbeatIntervalMs(); if (isValidHeartbeatInterval(intervalInConfig)) { RecordLog.info("[HeartbeatSenderInitFunc] Using heartbeat interval " + "in Sentinel config property: " + intervalInConfig); return intervalInConfig; } else { long senderInterval = sender.intervalMs(); RecordLog.info("[HeartbeatSenderInit] Heartbeat interval not configured in " + "config property or invalid, using sender default: " + senderInterval); return senderInterval; } } private void scheduleHeartbeatTask(/*@NonNull*/ final HeartbeatSender sender, /*@Valid*/ long interval) { pool.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { sender.sendHeartbeat(); } catch (Throwable e) { RecordLog.warn("[HeartbeatSender] Send heartbeat error", e); } } }, 5000, interval, TimeUnit.MILLISECONDS); RecordLog.info("[HeartbeatSenderInit] HeartbeatSender started: " + sender.getClass().getCanonicalName()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/log/CommandCenterLog.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.log; import com.alibaba.csp.sentinel.log.LoggerSpiProvider; import com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter; /** * Logger for command center. * * @author Eric Zhao */ public class CommandCenterLog { public static final String LOGGER_NAME = "sentinelCommandCenterLogger"; public static final String DEFAULT_LOG_FILENAME = "command-center.log"; private static com.alibaba.csp.sentinel.log.Logger logger = null; static { try { // Load user-defined logger implementation first. logger = LoggerSpiProvider.getLogger(LOGGER_NAME); if (logger == null) { // If no customized loggers are provided, we use the default logger based on JUL. logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME); } } catch (Throwable t) { System.err.println("Error: failed to initialize Sentinel CommandCenterLog"); t.printStackTrace(); } } public static void info(String format, Object... arguments) { logger.info(format, arguments); } public static void info(String msg, Throwable e) { logger.info(msg, e); } public static void warn(String format, Object... arguments) { logger.warn(format, arguments); } public static void warn(String msg, Throwable e) { logger.warn(msg, e); } public static void trace(String format, Object... arguments) { logger.trace(format, arguments); } public static void trace(String msg, Throwable e) { logger.trace(msg, e); } public static void debug(String format, Object... arguments) { logger.debug(format, arguments); } public static void debug(String msg, Throwable e) { logger.debug(msg, e); } public static void error(String format, Object... arguments) { logger.error(format, arguments); } public static void error(String msg, Throwable e) { logger.error(msg, e); } private CommandCenterLog() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/ssl/SslFactory.java ================================================ package com.alibaba.csp.sentinel.transport.ssl; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author Leo Li */ public class SslFactory { private static class SslContextInstance { private static final SSLContext SSL_CONTEXT = initSslContext(); } private static SSLContext initSslContext() { SSLContext sslContext = null; try { sslContext = SSLContext.getInstance("TLS"); X509TrustManager x509TrustManager = new X509TrustManager() { public boolean isServerTrusted(X509Certificate[] certs) { return true; } public boolean isClientTrusted(X509Certificate[] certs) { return true; } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[] { x509TrustManager }, null); } catch (Exception e) { RecordLog.error("get ssl socket factory error", e); } return sslContext; } public static SSLContext getSslConnectionSocketFactory() { return SslContextInstance.SSL_CONTEXT; } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/util/HttpCommandUtils.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.util; import com.alibaba.csp.sentinel.command.CommandRequest; /** * Util class for HTTP command center. * * @author Eric Zhao */ public final class HttpCommandUtils { public static final String REQUEST_TARGET = "command-target"; public static String getTarget(CommandRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } return request.getMetadata().get(REQUEST_TARGET); } private HttpCommandUtils() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/util/WritableDataSourceRegistry.java ================================================ package com.alibaba.csp.sentinel.transport.util; import java.util.List; import com.alibaba.csp.sentinel.datasource.WritableDataSource; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * Writable data source registry for modifying rules via HTTP API. * * @author Eric Zhao */ public final class WritableDataSourceRegistry { private static WritableDataSource> flowDataSource = null; private static WritableDataSource> authorityDataSource = null; private static WritableDataSource> degradeDataSource = null; private static WritableDataSource> systemSource = null; public static synchronized void registerFlowDataSource(WritableDataSource> datasource) { flowDataSource = datasource; } public static synchronized void registerAuthorityDataSource(WritableDataSource> dataSource) { authorityDataSource = dataSource; } public static synchronized void registerDegradeDataSource(WritableDataSource> dataSource) { degradeDataSource = dataSource; } public static synchronized void registerSystemDataSource(WritableDataSource> dataSource) { systemSource = dataSource; } public static WritableDataSource> getFlowDataSource() { return flowDataSource; } public static WritableDataSource> getAuthorityDataSource() { return authorityDataSource; } public static WritableDataSource> getDegradeDataSource() { return degradeDataSource; } public static WritableDataSource> getSystemSource() { return systemSource; } private WritableDataSourceRegistry() {} } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler com.alibaba.csp.sentinel.command.handler.FetchClusterNodeByIdCommandHandler com.alibaba.csp.sentinel.command.handler.FetchClusterNodeHumanCommandHandler com.alibaba.csp.sentinel.command.handler.FetchJsonTreeCommandHandler com.alibaba.csp.sentinel.command.handler.FetchOriginCommandHandler com.alibaba.csp.sentinel.command.handler.FetchSimpleClusterNodeCommandHandler com.alibaba.csp.sentinel.command.handler.FetchSystemStatusCommandHandler com.alibaba.csp.sentinel.command.handler.FetchTreeCommandHandler com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler com.alibaba.csp.sentinel.command.handler.VersionCommandHandler com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler com.alibaba.csp.sentinel.command.handler.ApiCommandHandler ================================================ FILE: sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc ================================================ com.alibaba.csp.sentinel.transport.init.CommandCenterInitFunc com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/command/GetRulesCommandHandlerInterceptor.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandRequestExecution; import com.alibaba.csp.sentinel.command.CommandResponse; /** * @author icodening * @date 2022.03.23 */ public class GetRulesCommandHandlerInterceptor implements CommandHandlerInterceptor { @Override public boolean shouldIntercept(String commandName) { return "getRules".equals(commandName); } @Override public CommandResponse intercept(CommandRequest request, CommandRequestExecution execution) { String type = request.getParam("type"); System.out.println("[GetRulesCommandHandlerInterceptor] get rules for [" + type + "]"); return execution.execute(request); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/command/InterceptingCommandHandlerTest.java ================================================ /* * Copyright 1999-2022 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.handler.InterceptingCommandHandler; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils; import com.alibaba.csp.sentinel.util.AssertUtil; import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.Map; /** * @author icodening * @date 2022.03.23 */ @SuppressWarnings("all") public class InterceptingCommandHandlerTest { private Map commandHandlerMap = CommandHandlerProvider.getInstance().namedHandlers(); @Before public void setUp() throws Exception { FlowRule flowRule = new FlowRule(); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS) .setClusterMode(false) .setCount(1) .setResource("/test"); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); } @Test public void testInterceptCommand() { CommandHandler getRulesHandler = commandHandlerMap.get("getRules"); AssertUtil.assertState(getRulesHandler instanceof InterceptingCommandHandler, "getRulesHandler should be an InterceptingCommandHandler"); CommandHandler basicInfoHandler = commandHandlerMap.get("basicInfo"); AssertUtil.assertState(!(basicInfoHandler instanceof InterceptingCommandHandler), "basicInfoHandler should not be an InterceptingCommandHandler"); CommandRequest getRulesCommandRequest = new CommandRequest(); getRulesCommandRequest.addMetadata(HttpCommandUtils.REQUEST_TARGET, "getRules"); getRulesCommandRequest.addParam("type", "flow"); CommandResponse getRulesResponse = getRulesHandler.handle(getRulesCommandRequest); System.out.println(getRulesResponse.getResult()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/config/TransportConfigTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.config; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import java.util.List; public class TransportConfigTest { @Before public void setUp() throws Exception { SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS); SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_CLIENT_IP); } @After public void tearDown() throws Exception { SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS); SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_CLIENT_IP); } @Test public void testGetHeartbeatInterval() { long interval = 20000; assertNull(TransportConfig.getHeartbeatIntervalMs()); // Set valid interval. SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(interval)); assertEquals(new Long(interval), TransportConfig.getHeartbeatIntervalMs()); // Set invalid interval. SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, "Sentinel"); assertNull(TransportConfig.getHeartbeatIntervalMs()); } @Test public void testGetHeartbeatClientIp() { String clientIp = "10.10.10.10"; SentinelConfig.setConfig(TransportConfig.HEARTBEAT_CLIENT_IP, clientIp); // Set heartbeat client ip to system property. String ip = TransportConfig.getHeartbeatClientIp(); assertNotNull(ip); assertEquals(clientIp, ip); // Set no heartbeat client ip. SentinelConfig.setConfig(TransportConfig.HEARTBEAT_CLIENT_IP, ""); assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatClientIp())); } @Test public void testGetHeartbeatApiPath() { // use default heartbeat api path assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatApiPath())); assertEquals(TransportConfig.HEARTBEAT_DEFAULT_PATH, TransportConfig.getHeartbeatApiPath()); // config heartbeat api path SentinelConfig.setConfig(TransportConfig.HEARTBEAT_API_PATH, "/demo"); assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatApiPath())); assertEquals("/demo", TransportConfig.getHeartbeatApiPath()); SentinelConfig.setConfig(TransportConfig.HEARTBEAT_API_PATH, "demo/registry"); assertEquals("/demo/registry", TransportConfig.getHeartbeatApiPath()); SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_API_PATH); assertEquals(TransportConfig.HEARTBEAT_DEFAULT_PATH, TransportConfig.getHeartbeatApiPath()); } @Test public void testGetConsoleServerList() { // empty SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ""); List list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); // single ip SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "112.13.223.3"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(1, list.size()); assertEquals("112.13.223.3", list.get(0).getHost()); assertEquals(80, list.get(0).getPort()); // single domain SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(1, list.size()); assertEquals("www.dashboard.org", list.get(0).getHost()); assertEquals(80, list.get(0).getPort()); // single ip including port SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:81"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(1, list.size()); assertEquals("www.dashboard.org", list.get(0).getHost()); assertEquals(81, list.get(0).getPort()); // mixed SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:81,112.13.223.3,112.13.223.4:8080,www.dashboard.org"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(4, list.size()); assertEquals("www.dashboard.org", list.get(0).getHost()); assertEquals(81, list.get(0).getPort()); assertEquals("112.13.223.3", list.get(1).getHost()); assertEquals(80, list.get(1).getPort()); assertEquals("112.13.223.4", list.get(2).getHost()); assertEquals(8080, list.get(2).getPort()); assertEquals("www.dashboard.org", list.get(3).getHost()); assertEquals(80, list.get(3).getPort()); // malformed SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:0"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:-1"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ":80"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:80000"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(0, list.size()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:80000,www.dashboard.org:81,:80"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(1, list.size()); assertEquals("www.dashboard.org", list.get(0).getHost()); assertEquals(81, list.get(0).getPort()); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "https://www.dashboard.org,http://www.dashboard.org:8080,www.dashboard.org,www.dashboard.org:8080"); list = TransportConfig.getConsoleServerList(); assertNotNull(list); assertEquals(4, list.size()); assertEquals(Protocol.HTTPS, list.get(0).getProtocol()); assertEquals(Protocol.HTTP, list.get(1).getProtocol()); assertEquals(Protocol.HTTP, list.get(2).getProtocol()); assertEquals(Protocol.HTTP, list.get(3).getProtocol()); assertEquals(443, list.get(0).getPort()); assertEquals(80, list.get(2).getPort()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/endpoint/EndpointTest.java ================================================ package com.alibaba.csp.sentinel.transport.endpoint; import static org.junit.Assert.assertEquals; import org.junit.Test; public class EndpointTest { @Test public void testToStringIPv4() { Endpoint e = new Endpoint(Protocol.HTTP, "127.0.0.1", 8080); assertEquals("Endpoint{protocol=HTTP, host='127.0.0.1', port=8080}", e.toString()); } @Test public void testToStringIPv6() { Endpoint e = new Endpoint(Protocol.HTTP, "fe80::1", 8080); // Endpoint#toString doesn't modify the host, so IPv6 remains unbracketed assertEquals("Endpoint{protocol=HTTP, host='fe80::1', port=8080}", e.toString()); } @Test public void testToStringAlreadyBracketed() { Endpoint e = new Endpoint(Protocol.HTTPS, "[fe80::2]", 443); assertEquals("Endpoint{protocol=HTTPS, host='[fe80::2]', port=443}", e.toString()); } @Test public void testToStringNullHost() { Endpoint e = new Endpoint(Protocol.HTTP, null, 0); assertEquals("Endpoint{protocol=HTTP, host='null', port=0}", e.toString()); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/init/HeartbeatSenderInitFuncTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.init; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** * @author Eric Zhao */ public class HeartbeatSenderInitFuncTest { @Before public void setUp() throws Exception { SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS); } @After public void tearDown() throws Exception { SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS); } @Test public void testRetrieveInterval() { HeartbeatSender sender = mock(HeartbeatSender.class); long senderInterval = 5666; long configInterval = 6777; when(sender.intervalMs()).thenReturn(senderInterval); HeartbeatSenderInitFunc func = new HeartbeatSenderInitFunc(); assertEquals(senderInterval, func.retrieveInterval(sender)); // Invalid interval. SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, "-1"); assertEquals(senderInterval, func.retrieveInterval(sender)); SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(configInterval)); assertEquals(configInterval, func.retrieveInterval(sender)); } } ================================================ FILE: sentinel-transport/sentinel-transport-common/src/test/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandlerInterceptor ================================================ com.alibaba.csp.sentinel.transport.command.GetRulesCommandHandlerInterceptor ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/pom.xml ================================================ com.alibaba.csp sentinel-transport ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-transport-netty-http jar 4.1.31.Final com.alibaba.csp sentinel-transport-common ${project.version} io.netty netty-all ${netty.version} com.alibaba fastjson org.apache.httpcomponents httpclient 4.5.3 junit junit test org.mockito mockito-core test ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/NettyHttpCommandCenter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.transport.command.netty.HttpServer; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.CommandCenter; /** * Implementation of {@link CommandCenter} based on Netty HTTP library. * * @author Eric Zhao */ @Spi(order = Spi.ORDER_LOWEST - 100) public class NettyHttpCommandCenter implements CommandCenter { private final HttpServer server = new HttpServer(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private final ExecutorService pool = Executors.newSingleThreadExecutor( new NamedThreadFactory("sentinel-netty-command-center-executor", true)); @Override public void start() throws Exception { pool.submit(new Runnable() { @Override public void run() { try { server.start(); } catch (Exception ex) { RecordLog.warn("[NettyHttpCommandCenter] Failed to start Netty transport server", ex); ex.printStackTrace(); } } }); } @Override public void stop() throws Exception { server.close(); pool.shutdownNow(); } @Override public void beforeStart() throws Exception { // Register handlers Map handlers = CommandHandlerProvider.getInstance().namedHandlers(); server.registerCommands(handlers); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/CodecRegistry.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; import java.util.ArrayList; import java.util.List; /** * @author Eric Zhao */ public final class CodecRegistry { private final List> encoderList = new ArrayList>(); private final List> decoderList = new ArrayList>(); public CodecRegistry() { // Register default codecs. registerEncoder(DefaultCodecs.STRING_ENCODER); registerDecoder(DefaultCodecs.STRING_DECODER); } public void registerEncoder(Encoder encoder) { encoderList.add(encoder); } public void registerDecoder(Decoder decoder) { decoderList.add(decoder); } public List> getEncoderList() { return encoderList; } public List> getDecoderList() { return decoderList; } public void reset() { encoderList.clear(); decoderList.clear(); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/Decoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; import java.nio.charset.Charset; /** * The decoder decodes bytes into an object of type {@code }. * * @param target type * @author Eric Zhao */ public interface Decoder { /** * Check whether the decoder supports the given target type. * * @param clazz type of the class * @return {@code true} if supported, {@code false} otherwise */ boolean canDecode(Class clazz); /** * Decode the given byte array into an object of type {@code R} with the default charset. * * @param bytes raw byte buffer * @return the decoded target object * @throws Exception error occurs when decoding the object (e.g. IO fails) */ R decode(byte[] bytes) throws Exception; /** * Decode the given byte array into an object of type {@code R} with the given charset. * * @param bytes raw byte buffer * @param charset the charset * @return the decoded target object * @throws Exception error occurs when decoding the object (e.g. IO fails) */ R decode(byte[] bytes, Charset charset) throws Exception; } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/DefaultCodecs.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; /** * Caches default encoders and decoders. * * @author Eric Zhao */ final class DefaultCodecs { public static final Encoder STRING_ENCODER = new StringEncoder(); public static final Decoder STRING_DECODER = new StringDecoder(); private DefaultCodecs() {} } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/Encoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; import java.nio.charset.Charset; /** * The encoder encodes an object of type {@code } into byte array. * * @param source type * @author Eric Zhao */ public interface Encoder { /** * Check whether the encoder supports the given source type. * * @param clazz type of the class * @return {@code true} if supported, {@code false} otherwise */ boolean canEncode(Class clazz); /** * Encode the given object into a byte array with the given charset. * * @param r the object to encode * @param charset the charset * @return the encoded byte buffer * @throws Exception error occurs when encoding the object (e.g. IO fails) */ byte[] encode(R r, Charset charset) throws Exception; /** * Encode the given object into a byte array with the default charset. * * @param r the object to encode * @return the encoded byte buffer, which is already flipped. * @throws Exception error occurs when encoding the object (e.g. IO fails) */ byte[] encode(R r) throws Exception; } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/StringDecoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; import java.nio.charset.Charset; import com.alibaba.csp.sentinel.config.SentinelConfig; /** * Decodes from a byte array to string. * * @author Eric Zhao */ public class StringDecoder implements Decoder { @Override public boolean canDecode(Class clazz) { return String.class.isAssignableFrom(clazz); } @Override public String decode(byte[] bytes) throws Exception { return decode(bytes, Charset.forName(SentinelConfig.charset())); } @Override public String decode(byte[] bytes, Charset charset) { if (bytes == null || bytes.length <= 0) { throw new IllegalArgumentException("Bad byte array"); } return new String(bytes, charset); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/codec/StringEncoder.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.codec; import java.nio.charset.Charset; import com.alibaba.csp.sentinel.config.SentinelConfig; /** * Encode a string to a byte array. * * @author Eric Zhao */ public class StringEncoder implements Encoder { @Override public boolean canEncode(Class clazz) { return String.class.isAssignableFrom(clazz); } @Override public byte[] encode(String string, Charset charset) { return string.getBytes(charset); } @Override public byte[] encode(String s) { return encode(s, Charset.forName(SentinelConfig.charset())); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * @author Eric Zhao */ @SuppressWarnings("rawtypes") public final class HttpServer { private static final int DEFAULT_PORT = 8719; private Channel channel; final static Map handlerMap = new ConcurrentHashMap(); public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new HttpServerInitializer()); int port; try { if (StringUtil.isEmpty(TransportConfig.getPort())) { CommandCenterLog.info("Port not configured, using default port: " + DEFAULT_PORT); port = DEFAULT_PORT; } else { port = Integer.parseInt(TransportConfig.getPort()); } } catch (Exception e) { // Will cause the application exit. throw new IllegalArgumentException("Illegal port: " + TransportConfig.getPort()); } int retryCount = 0; ChannelFuture channelFuture = null; // loop for an successful binding while (true) { int newPort = getNewPort(port, retryCount); try { channelFuture = b.bind(newPort).sync(); TransportConfig.setRuntimePort(newPort); CommandCenterLog.info("[NettyHttpCommandCenter] Begin listening at port " + newPort); break; } catch (Exception e) { TimeUnit.MILLISECONDS.sleep(30); RecordLog.warn("[HttpServer] Netty server bind error, port={}, retry={}", newPort, retryCount); retryCount ++; } } channel = channelFuture.channel(); channel.closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } /** * Increase port number every 3 tries. * * @param basePort base port to start * @param retryCount retry count * @return next calculated port */ private int getNewPort(int basePort, int retryCount) { return basePort + retryCount / 3; } public void close() { channel.close(); } public void registerCommand(String commandName, CommandHandler handler) { if (StringUtil.isEmpty(commandName) || handler == null) { return; } if (handlerMap.containsKey(commandName)) { CommandCenterLog.warn("[NettyHttpCommandCenter] Register failed (duplicate command): " + commandName); return; } handlerMap.put(commandName, handler); } public void registerCommands(Map handlerMap) { if (handlerMap != null) { for (Entry e : handlerMap.entrySet()) { registerCommand(e.getKey(), e.getValue()); } } } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.transport.command.codec.CodecRegistry; import com.alibaba.csp.sentinel.transport.command.codec.Encoder; import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils; import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.multipart.HttpData; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.InterfaceHttpData; import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static io.netty.handler.codec.http.HttpResponseStatus.OK; /** * Netty-based HTTP server handler for command center. * * Note: HTTP chunked is not tested! * * @author Eric Zhao */ public class HttpServerHandler extends SimpleChannelInboundHandler { private final CodecRegistry codecRegistry = new CodecRegistry(); @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { FullHttpRequest httpRequest = (FullHttpRequest)msg; try { CommandRequest request = parseRequest(httpRequest); if (StringUtil.isBlank(HttpCommandUtils.getTarget(request))) { writeErrorResponse(BAD_REQUEST.code(), "Invalid command", ctx); return; } handleRequest(request, ctx, HttpUtil.isKeepAlive(httpRequest)); } catch (Exception ex) { writeErrorResponse(INTERNAL_SERVER_ERROR.code(), SERVER_ERROR_MESSAGE, ctx); CommandCenterLog.warn("Internal error", ex); } } private void handleRequest(CommandRequest request, ChannelHandlerContext ctx, boolean keepAlive) throws Exception { String commandName = HttpCommandUtils.getTarget(request); // Find the matching command handler. CommandHandler commandHandler = getHandler(commandName); if (commandHandler != null) { CommandResponse response = commandHandler.handle(request); writeResponse(response, ctx, keepAlive); } else { // No matching command handler. writeErrorResponse(BAD_REQUEST.code(), String.format("Unknown command \"%s\"", commandName), ctx); } } private Encoder pickEncoder(Class clazz) { if (clazz == null) { throw new IllegalArgumentException("Bad class metadata"); } for (Encoder encoder : codecRegistry.getEncoderList()) { if (encoder.canEncode(clazz)) { return encoder; } } return null; } private void writeErrorResponse(int statusCode, String message, ChannelHandlerContext ctx) { FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(statusCode), Unpooled.copiedBuffer(message, Charset.forName(SentinelConfig.charset()))); httpResponse.headers().set("Content-Type", "text/plain; charset=" + SentinelConfig.charset()); ctx.write(httpResponse); ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } private void writeResponse(CommandResponse response, ChannelHandlerContext ctx, boolean keepAlive) throws Exception { byte[] body; if (response.isSuccess()) { if (response.getResult() == null) { body = new byte[] {}; } else { Encoder encoder = pickEncoder(response.getResult().getClass()); if (encoder == null) { writeErrorResponse(INTERNAL_SERVER_ERROR.code(), SERVER_ERROR_MESSAGE, ctx); CommandCenterLog.warn("Error when encoding object", new IllegalStateException("No compatible encoder")); return; } body = encoder.encode(response.getResult()); } } else { body = response.getException().getMessage().getBytes(SentinelConfig.charset()); } HttpResponseStatus status = response.isSuccess() ? OK : BAD_REQUEST; FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(body)); httpResponse.headers().set("Content-Type", "text/plain; charset=" + SentinelConfig.charset()); //if (keepAlive) { // httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes()); // httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); //} //ctx.write(httpResponse); //if (!keepAlive) { // ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); //} httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes()); httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); ctx.write(httpResponse); ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } private CommandRequest parseRequest(FullHttpRequest request) { QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri()); CommandRequest serverRequest = new CommandRequest(); Map> paramMap = queryStringDecoder.parameters(); // Parse request parameters. if (!paramMap.isEmpty()) { for (Entry> p : paramMap.entrySet()) { if (!p.getValue().isEmpty()) { serverRequest.addParam(p.getKey(), p.getValue().get(0)); } } } // Deal with post method, parameter in post has more privilege compared to that in querystring if (request.method().equals(HttpMethod.POST)) { // support multi-part and form-urlencoded HttpPostRequestDecoder postRequestDecoder = null; try { postRequestDecoder = new HttpPostRequestDecoder(request); for (InterfaceHttpData data : postRequestDecoder.getBodyHttpDatas()) { data.retain(); // must retain each attr before destroy if (data.getHttpDataType() == HttpDataType.Attribute) { if (data instanceof HttpData) { HttpData httpData = (HttpData) data; try { String name = httpData.getName(); String value = httpData.getString(); serverRequest.addParam(name, value); } catch (IOException e) { } } } } } finally { if (postRequestDecoder != null) { postRequestDecoder.destroy(); } } } // Parse command name. String target = parseTarget(queryStringDecoder.rawPath()); serverRequest.addMetadata(HttpCommandUtils.REQUEST_TARGET, target); // Parse body. if (request.content().readableBytes() <= 0) { serverRequest.setBody(null); } else { byte[] body = new byte[request.content().readableBytes()]; request.content().getBytes(0, body); serverRequest.setBody(body); } return serverRequest; } private String parseTarget(String uri) { if (StringUtil.isEmpty(uri)) { return ""; } // Remove the / of the uri as the target(command name) // Usually the uri is start with / int start = uri.indexOf('/'); if (start != -1) { return uri.substring(start + 1); } return uri; } private CommandHandler getHandler(String commandName) { if (StringUtil.isEmpty(commandName)) { return null; } return HttpServer.handlerMap.get(commandName); } private void send100Continue(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); ctx.write(response); } private static final String SERVER_ERROR_MESSAGE = "Command server error"; } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerInitializer.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; /** * @author Eric Zhao */ public class HttpServerInitializer extends ChannelInitializer { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline p = socketChannel.pipeline(); p.addLast(new HttpRequestDecoder()); p.addLast(new HttpObjectAggregator(1024 * 1024)); p.addLast(new HttpResponseEncoder()); p.addLast(new HttpServerHandler()); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HttpHeartbeatSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import com.alibaba.csp.sentinel.transport.heartbeat.client.HttpClientsFactory; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.csp.sentinel.util.PidUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import java.util.List; /** * @author Eric Zhao * @author Carpenter Lee * @author Leo Li */ @Spi(order = Spi.ORDER_LOWEST - 100) public class HttpHeartbeatSender implements HeartbeatSender { private final CloseableHttpClient client; private static final int OK_STATUS = 200; private final int timeoutMs = 3000; private final RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(timeoutMs) .setConnectTimeout(timeoutMs) .setSocketTimeout(timeoutMs) .build(); private final Protocol consoleProtocol; private final String consoleHost; private final int consolePort; public HttpHeartbeatSender() { List dashboardList = TransportConfig.getConsoleServerList(); if (dashboardList == null || dashboardList.isEmpty()) { RecordLog.info("[NettyHttpHeartbeatSender] No dashboard server available"); consoleProtocol = Protocol.HTTP; consoleHost = null; consolePort = -1; } else { consoleProtocol = dashboardList.get(0).getProtocol(); consoleHost = dashboardList.get(0).getHost(); consolePort = dashboardList.get(0).getPort(); RecordLog.info("[NettyHttpHeartbeatSender] Dashboard address parsed: <{}:{}>", consoleHost, consolePort); } this.client = HttpClientsFactory.getHttpClientsByProtocol(consoleProtocol); } @Override public boolean sendHeartbeat() throws Exception { if (StringUtil.isEmpty(consoleHost)) { return false; } URIBuilder uriBuilder = new URIBuilder(); uriBuilder.setScheme(consoleProtocol.getProtocol()).setHost(consoleHost).setPort(consolePort) .setPath(TransportConfig.getHeartbeatApiPath()) .setParameter("app", AppNameUtil.getAppName()) .setParameter("app_type", String.valueOf(SentinelConfig.getAppType())) .setParameter("v", Constants.SENTINEL_VERSION) .setParameter("version", String.valueOf(System.currentTimeMillis())) .setParameter("hostname", HostNameUtil.getHostName()) .setParameter("ip", TransportConfig.getHeartbeatClientIp()) .setParameter("port", TransportConfig.getPort()) .setParameter("pid", String.valueOf(PidUtil.getPid())); HttpGet request = new HttpGet(uriBuilder.build()); request.setConfig(requestConfig); // Send heartbeat request. CloseableHttpResponse response = client.execute(request); response.close(); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == OK_STATUS) { return true; } else if (clientErrorCode(statusCode) || serverErrorCode(statusCode)) { RecordLog.warn("[HttpHeartbeatSender] Failed to send heartbeat to " + consoleHost + ":" + consolePort + ", http status code: " + statusCode); } return false; } @Override public long intervalMs() { return 5000; } private boolean clientErrorCode(int code) { return code > 399 && code < 500; } private boolean serverErrorCode(int code) { return code > 499 && code < 600; } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/HttpClientsFactory.java ================================================ package com.alibaba.csp.sentinel.transport.heartbeat.client; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import com.alibaba.csp.sentinel.transport.ssl.SslFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; /** * @author Leo Li */ public class HttpClientsFactory { private static class SslConnectionSocketFactoryInstance { private static final SSLConnectionSocketFactory SSL_CONNECTION_SOCKET_FACTORY = new SSLConnectionSocketFactory(SslFactory.getSslConnectionSocketFactory(), NoopHostnameVerifier.INSTANCE); } public static CloseableHttpClient getHttpClientsByProtocol(Protocol protocol) { return protocol == Protocol.HTTP ? HttpClients.createDefault() : HttpClients.custom(). setSSLSocketFactory(SslConnectionSocketFactoryInstance.SSL_CONNECTION_SOCKET_FACTORY).build(); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter ================================================ com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender ================================================ com.alibaba.csp.sentinel.transport.heartbeat.HttpHeartbeatSender ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/test/java/com/alibaba/csp/sentinel/transport/command/handler/MultipleSlashNameCommandTestHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.handler; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.command.annotation.CommandMapping; /** * @author cdfive */ @CommandMapping(name = "aa/bb/cc", desc = "a test handler with multiple / in its name") public class MultipleSlashNameCommandTestHandler implements CommandHandler { @Override public CommandResponse handle(CommandRequest request) { return CommandResponse.ofSuccess("MultipleSlashNameCommandTestHandler result"); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/test/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerHandlerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.transport.CommandCenter; import com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter; import com.alibaba.csp.sentinel.transport.command.handler.MultipleSlashNameCommandTestHandler; import com.alibaba.fastjson.JSON; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.http.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static org.junit.Assert.assertEquals; /** * Test cases for {@link HttpServerHandler}. * * @author cdfive */ public class HttpServerHandlerTest { private static String CRLF = "\r\n"; private static String SENTINEL_CHARSET_NAME = SentinelConfig.charset(); private static Charset SENTINEL_CHARSET = Charset.forName(SENTINEL_CHARSET_NAME); private static EmbeddedChannel embeddedChannel; @BeforeClass public static void beforeClass() throws Exception { // Don't execute InitExecutor.doInit, to avoid CommandCenter SPI loaded Field[] declaredFields = InitExecutor.class.getDeclaredFields(); for (Field declaredField : declaredFields) { if (declaredField.getName().equals("initialized")) { declaredField.setAccessible(true); ((AtomicBoolean)declaredField.get(InitExecutor.class)).set(true); } } // Create NettyHttpCommandCenter to create HttpServer CommandCenter commandCenter = new NettyHttpCommandCenter(); // Call beforeStart to register handlers commandCenter.beforeStart(); } @Before public void before() { // The same Handlers in order as the ChannelPipeline in HttpServerInitializer HttpRequestDecoder httpRequestDecoder = new HttpRequestDecoder(); HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(1024 * 1024); HttpResponseEncoder httpResponseEncoder = new HttpResponseEncoder(); HttpServerHandler httpServerHandler = new HttpServerHandler(); // Create new EmbeddedChannel every method call embeddedChannel = new EmbeddedChannel(httpRequestDecoder, httpObjectAggregator, httpResponseEncoder, httpServerHandler); // Clear flow rules FlowRuleManager.loadRules(Collections.EMPTY_LIST); } @Test public void testInvalidCommand() { String httpRequestStr = "GET / HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = "Invalid command"; processError(httpRequestStr, expectedBody); } @Test public void testUnknownCommand() { String httpRequestStr = "GET /aaa HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = String.format("Unknown command \"%s\"", "aaa"); processError(httpRequestStr, expectedBody); } /** * {@link com.alibaba.csp.sentinel.command.handler.VersionCommandHandler} */ @Test public void testVersionCommand() { String httpRequestStr = "GET /version HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = Constants.SENTINEL_VERSION; processSuccess(httpRequestStr, expectedBody); } /** * {@link com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler} */ @Test public void testFetchActiveRuleCommandInvalidType() { String httpRequestStr = "GET /getRules HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = "invalid type"; processFailed(httpRequestStr, expectedBody); } @Test public void testFetchActiveRuleCommandEmptyRule() { String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = "[]"; processSuccess(httpRequestStr, expectedBody); } @Test public void testFetchActiveRuleCommandSomeFlowRules() { List rules = new ArrayList(); FlowRule rule1 = new FlowRule(); rule1.setResource("key"); rule1.setCount(20); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; // body json /* String expectedBody = "[{\"clusterMode\":false,\"controlBehavior\":0,\"count\":20.0" + ",\"grade\":1,\"limitApp\":\"default\",\"maxQueueingTimeMs\":500" + ",\"resource\":\"key\",\"strategy\":0,\"warmUpPeriodSec\":10}]"; */ String expectedBody = JSON.toJSONString(rules); processSuccess(httpRequestStr, expectedBody); } /** * {@link MultipleSlashNameCommandTestHandler} * * Test command whose mapping path and command name contain multiple / */ @Test public void testMultipleSlashNameCommand() { String httpRequestStr = "GET /aa/bb/cc HTTP/1.1" + CRLF + "Host: localhost:8719" + CRLF + CRLF; String expectedBody = "MultipleSlashNameCommandTestHandler result"; processSuccess(httpRequestStr, expectedBody); } private void processError(String httpRequestStr, String expectedBody) { processError(httpRequestStr, BAD_REQUEST, expectedBody); } private void processError(String httpRequestStr, HttpResponseStatus status, String expectedBody) { String httpResponseStr = processResponse(httpRequestStr); assertErrorStatusAndBody(status, expectedBody, httpResponseStr); } private void processSuccess(String httpRequestStr, String expectedBody) { process(httpRequestStr, OK, expectedBody); } private void processFailed(String httpRequestStr, String expectedBody) { process(httpRequestStr, BAD_REQUEST, expectedBody); } private void process(String httpRequestStr, HttpResponseStatus status, String expectedBody) { String responseStr = processResponse(httpRequestStr); assertStatusAndBody(status, expectedBody, responseStr); } private String processResponse(String httpRequestStr) { embeddedChannel.writeInbound(Unpooled.wrappedBuffer(httpRequestStr.getBytes(SENTINEL_CHARSET))); StringBuilder sb = new StringBuilder(); ByteBuf byteBuf; while ((byteBuf = embeddedChannel.readOutbound()) != null) { sb.append(byteBuf.toString(SENTINEL_CHARSET)); } return sb.toString(); } private void assertErrorStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) { StringBuilder text = new StringBuilder(); text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF); text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF); text.append(CRLF); text.append(expectedBody); assertEquals(text.toString(), httpResponseStr); } private void assertStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) { StringBuilder text = new StringBuilder(); text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF); text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF); text.append("content-length: " + expectedBody.length()).append(CRLF); text.append("connection: close").append(CRLF); text.append(CRLF); text.append(expectedBody); assertEquals(text.toString(), httpResponseStr); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/test/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerInitializerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import org.junit.Test; import org.mockito.InOrder; import static org.mockito.Mockito.*; /** * Test cases for {@link HttpServerInitializer}. * * @author cdfive */ public class HttpServerInitializerTest { @Test public void testInitChannel() throws Exception { // Mock Objects HttpServerInitializer httpServerInitializer = mock(HttpServerInitializer.class); SocketChannel socketChannel = mock(SocketChannel.class); ChannelPipeline channelPipeline = mock(ChannelPipeline.class); // Mock SocketChannel#pipeline() method when(socketChannel.pipeline()).thenReturn(channelPipeline); // HttpServerInitializer#initChannel(SocketChannel) call real method doCallRealMethod().when(httpServerInitializer).initChannel(socketChannel); // Start test for HttpServerInitializer#initChannel(SocketChannel) httpServerInitializer.initChannel(socketChannel); // Verify 4 times calling ChannelPipeline#addLast() method verify(channelPipeline, times(4)).addLast(any(ChannelHandler.class)); // Verify the order of calling ChannelPipeline#addLast() method InOrder inOrder = inOrder(channelPipeline); inOrder.verify(channelPipeline).addLast(any(HttpRequestDecoder.class)); inOrder.verify(channelPipeline).addLast(any(HttpObjectAggregator.class)); inOrder.verify(channelPipeline).addLast(any(HttpResponseEncoder.class)); inOrder.verify(channelPipeline).addLast(any(HttpServerHandler.class)); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/test/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.netty; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler; import com.alibaba.csp.sentinel.command.handler.VersionCommandHandler; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.util.Map; import static org.junit.Assert.*; /** * Test cases for {@link HttpServer}. * * @author cdfive */ public class HttpServerTest { private static HttpServer httpServer; @BeforeClass public static void beforeClass() { // Note: clear handlerMap first, as other test case may init HttpServer.handlerMap // if not, run mvn test, the next assertEquals(0, HttpServer.handlerMap.size()) may fail HttpServer.handlerMap.clear(); // Create new HttpServer httpServer = new HttpServer(); // No handler in handlerMap at first assertEquals(0, HttpServer.handlerMap.size()); } @Before public void before() { // Clear handlerMap every method call HttpServer.handlerMap.clear(); } @Test public void testRegisterCommand() { String commandName; CommandHandler handler; // If commandName is null, no handler added in handlerMap commandName = null; handler = new VersionCommandHandler(); httpServer.registerCommand(commandName, handler); assertEquals(0, HttpServer.handlerMap.size()); // If commandName is "", no handler added in handlerMap commandName = ""; handler = new VersionCommandHandler(); httpServer.registerCommand(commandName, handler); assertEquals(0, HttpServer.handlerMap.size()); // If handler is null, no handler added in handlerMap commandName = "version"; handler = null; httpServer.registerCommand(commandName, handler); assertEquals(0, HttpServer.handlerMap.size()); // Add one handler, commandName:version, handler:VersionCommandHandler commandName = "version"; handler = new VersionCommandHandler(); httpServer.registerCommand(commandName, handler); assertEquals(1, HttpServer.handlerMap.size()); // Add the same name Handler, no handler added in handlerMap commandName = "version"; handler = new VersionCommandHandler(); httpServer.registerCommand(commandName, handler); assertEquals(1, HttpServer.handlerMap.size()); // Add another handler, commandName:basicInfo, handler:BasicInfoCommandHandler commandName = "basicInfo"; handler = new BasicInfoCommandHandler(); httpServer.registerCommand(commandName, handler); assertEquals(2, HttpServer.handlerMap.size()); } @Test public void testRegisterCommands() { Map handlerMap = null; // If handlerMap is null, no handler added in handlerMap httpServer.registerCommands(handlerMap); assertEquals(0, HttpServer.handlerMap.size()); // Add handler from CommandHandlerProvider handlerMap = CommandHandlerProvider.getInstance().namedHandlers(); httpServer.registerCommands(handlerMap); // Check same size assertEquals(handlerMap.size(), HttpServer.handlerMap.size()); // Check not same reference assertTrue(handlerMap != HttpServer.handlerMap); } } ================================================ FILE: sentinel-transport/sentinel-transport-netty-http/src/test/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler ================================================ com.alibaba.csp.sentinel.transport.command.handler.MultipleSlashNameCommandTestHandler ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/pom.xml ================================================ com.alibaba.csp sentinel-transport ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-transport-simple-http com.alibaba.csp sentinel-transport-common ${project.version} junit junit test ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/SimpleHttpCommandCenter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.transport.CommandCenter; import com.alibaba.csp.sentinel.transport.command.http.HttpEventTask; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.StringUtil; /*** * The simple command center provides service to exchange information. * * @author youji.zj */ public class SimpleHttpCommandCenter implements CommandCenter { private static final int PORT_UNINITIALIZED = -1; private static final int DEFAULT_SERVER_SO_TIMEOUT = 3000; private static final int DEFAULT_PORT = 8719; @SuppressWarnings("rawtypes") private static final Map handlerMap = new ConcurrentHashMap(); @SuppressWarnings("PMD.ThreadPoolCreationRule") private ExecutorService executor = Executors.newSingleThreadExecutor( new NamedThreadFactory("sentinel-command-center-executor", true)); private ExecutorService bizExecutor; private ServerSocket socketReference; @Override @SuppressWarnings("rawtypes") public void beforeStart() throws Exception { // Register handlers Map handlers = CommandHandlerProvider.getInstance().namedHandlers(); registerCommands(handlers); } @Override public void start() throws Exception { int nThreads = Runtime.getRuntime().availableProcessors(); this.bizExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10), new NamedThreadFactory("sentinel-command-center-service-executor", true), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { CommandCenterLog.info("EventTask rejected"); throw new RejectedExecutionException(); } }); Runnable serverInitTask = new Runnable() { int port; { try { port = Integer.parseInt(TransportConfig.getPort()); } catch (Exception e) { port = DEFAULT_PORT; } } @Override public void run() { boolean success = false; ServerSocket serverSocket = getServerSocketFromBasePort(port); if (serverSocket != null) { CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort()); socketReference = serverSocket; executor.submit(new ServerThread(serverSocket)); success = true; port = serverSocket.getLocalPort(); } else { CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work"); } if (!success) { port = PORT_UNINITIALIZED; } TransportConfig.setRuntimePort(port); executor.shutdown(); } }; new Thread(serverInitTask).start(); } /** * Get a server socket from an available port from a base port.
          * Increasing on port number will occur when the port has already been used. * * @param basePort base port to start * @return new socket with available port */ private static ServerSocket getServerSocketFromBasePort(int basePort) { int tryCount = 0; while (true) { try { ServerSocket server = new ServerSocket(basePort + tryCount / 3, 100); server.setReuseAddress(true); return server; } catch (IOException e) { tryCount++; try { TimeUnit.MILLISECONDS.sleep(30); } catch (InterruptedException e1) { break; } } } return null; } @Override public void stop() throws Exception { if (socketReference != null) { try { socketReference.close(); } catch (IOException e) { CommandCenterLog.warn("Error when releasing the server socket", e); } } if (bizExecutor != null) { bizExecutor.shutdownNow(); } executor.shutdownNow(); TransportConfig.setRuntimePort(PORT_UNINITIALIZED); handlerMap.clear(); } /** * Get the name set of all registered commands. */ public static Set getCommands() { return handlerMap.keySet(); } class ServerThread extends Thread { private ServerSocket serverSocket; ServerThread(ServerSocket s) { this.serverSocket = s; setName("sentinel-courier-server-accept-thread"); } @Override public void run() { while (true) { Socket socket = null; try { socket = this.serverSocket.accept(); setSocketSoTimeout(socket); HttpEventTask eventTask = new HttpEventTask(socket); bizExecutor.submit(eventTask); } catch (Exception e) { CommandCenterLog.info("Server error", e); if (socket != null) { try { socket.close(); } catch (Exception e1) { CommandCenterLog.info("Error when closing an opened socket", e1); } } try { // In case of infinite log. Thread.sleep(10); } catch (InterruptedException e1) { // Indicates the task should stop. break; } } } } } @SuppressWarnings("rawtypes") public static CommandHandler getHandler(String commandName) { return handlerMap.get(commandName); } @SuppressWarnings("rawtypes") public static void registerCommands(Map handlerMap) { if (handlerMap != null) { for (Entry e : handlerMap.entrySet()) { registerCommand(e.getKey(), e.getValue()); } } } @SuppressWarnings("rawtypes") public static void registerCommand(String commandName, CommandHandler handler) { if (StringUtil.isEmpty(commandName)) { return; } if (handlerMap.containsKey(commandName)) { CommandCenterLog.warn("Register failed (duplicate command): " + commandName); return; } handlerMap.put(commandName, handler); } /** * Avoid server thread hang, 3 seconds timeout by default. */ private void setSocketSoTimeout(Socket socket) throws SocketException { if (socket != null) { socket.setSoTimeout(DEFAULT_SERVER_SO_TIMEOUT); } } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/exception/RequestException.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.exception; import com.alibaba.csp.sentinel.transport.command.http.StatusCode; /** * Represent exception with status code processing a request * * @author jason * */ public class RequestException extends Exception { private static final long serialVersionUID = 1L; private StatusCode statusCode = StatusCode.BAD_REQUEST; public RequestException() { super(); } public RequestException(StatusCode statusCode, String msg) { super(msg); this.statusCode = statusCode; } public StatusCode getStatusCode() { return statusCode; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTask.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.http; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter; import com.alibaba.csp.sentinel.transport.command.exception.RequestException; import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils; import com.alibaba.csp.sentinel.util.StringUtil; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * The task handles incoming command request in HTTP protocol. * * @author youji.zj * @author Eric Zhao * @author Jason Joo */ public class HttpEventTask implements Runnable { public static final String SERVER_ERROR_MESSAGE = "Command server error"; public static final String INVALID_COMMAND_MESSAGE = "Invalid command"; private final Socket socket; private boolean writtenHead = false; public HttpEventTask(Socket socket) { this.socket = socket; } public void close() throws Exception { socket.close(); } @Override public void run() { if (socket == null) { return; } PrintWriter printWriter = null; InputStream inputStream = null; try { long start = System.currentTimeMillis(); inputStream = new BufferedInputStream(socket.getInputStream()); OutputStream outputStream = socket.getOutputStream(); printWriter = new PrintWriter( new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset()))); String firstLine = readLine(inputStream); CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine + ", addr: " + socket.getInetAddress()); CommandRequest request = processQueryString(firstLine); if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) { // Deal with post method processPostRequest(inputStream, request); } // Validate the target command. String commandName = HttpCommandUtils.getTarget(request); if (StringUtil.isBlank(commandName)) { writeResponse(printWriter, StatusCode.BAD_REQUEST, INVALID_COMMAND_MESSAGE); return; } // Find the matching command handler. CommandHandler commandHandler = SimpleHttpCommandCenter.getHandler(commandName); if (commandHandler != null) { CommandResponse response = commandHandler.handle(request); handleResponse(response, printWriter); } else { // No matching command handler. writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`'); } long cost = System.currentTimeMillis() - start; CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + firstLine + ", address: " + socket.getInetAddress() + ", time cost: " + cost + " ms"); } catch (RequestException e) { writeResponse(printWriter, e.getStatusCode(), e.getMessage()); } catch (Throwable e) { CommandCenterLog.warn("[SimpleHttpCommandCenter] CommandCenter error", e); try { if (printWriter != null) { String errorMessage = SERVER_ERROR_MESSAGE; e.printStackTrace(); if (!writtenHead) { writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage); } else { printWriter.println(errorMessage); } printWriter.flush(); } } catch (Exception e1) { CommandCenterLog.warn("Failed to write error response", e1); } } finally { closeResource(inputStream); closeResource(printWriter); closeResource(socket); } } private static String readLine(InputStream in) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(64); int data; while (true) { data = in.read(); if (data < 0) { break; } if (data == '\n') { break; } bos.write(data); } byte[] arr = bos.toByteArray(); if (arr.length > 0 && arr[arr.length - 1] == '\r') { return new String(arr, 0, arr.length - 1, SentinelConfig.charset()); } return new String(arr, SentinelConfig.charset()); } /** * Try to process the body of POST request additionally. * * @param in * @param request * @throws RequestException * @throws IOException */ protected static void processPostRequest(InputStream in, CommandRequest request) throws RequestException, IOException { Map headerMap = parsePostHeaders(in); if (headerMap == null) { // illegal request CommandCenterLog.warn("Illegal request read: null headerMap"); throw new RequestException(StatusCode.BAD_REQUEST, ""); } if (headerMap.containsKey("content-type") && !checkContentTypeSupported(headerMap.get("content-type"))) { // not supported Content-type CommandCenterLog.warn("Request not supported: unsupported Content-Type: " + headerMap.get("content-type")); throw new RequestException(StatusCode.UNSUPPORTED_MEDIA_TYPE, "Only form-encoded post request is supported"); } int bodyLength = 0; try { bodyLength = Integer.parseInt(headerMap.get("content-length")); } catch (Exception e) { } if (bodyLength < 1) { // illegal request without Content-length header CommandCenterLog.warn("Request not supported: no available Content-Length in headers"); throw new RequestException(StatusCode.LENGTH_REQUIRED, "No legal Content-Length"); } parseParams(readBody(in, bodyLength), request); } /** * Process header line in request * * @param in * @return return headers in a Map, null for illegal request * @throws IOException */ protected static Map parsePostHeaders(InputStream in) throws IOException { Map headerMap = new HashMap(4); String line; while (true) { line = readLine(in); if (line == null || line.length() == 0) { // empty line return headerMap; } int index = line.indexOf(":"); if (index < 1) { // empty value, abandon continue; } String headerName = line.substring(0, index).trim().toLowerCase(); String headerValue = line.substring(index + 1).trim(); if (headerValue.length() > 0) { headerMap.put(headerName, headerValue); } } } private static boolean checkContentTypeSupported(String contentType) { int idx = contentType.indexOf(";"); String type; if (idx > 0) { type = contentType.substring(0, idx).toLowerCase().trim(); } else { type = contentType.toLowerCase(); } // Actually in RFC "x-*" shouldn't have any properties like "type/subtype; key=val" // But some library do add it. So we will be compatible with that but force to // encoding specified in configuration as legacy processing will do. if (!type.contains("application/x-www-form-urlencoded")) { // Not supported request type // Now simple-http only support form-encoded post request. return false; } return true; } private static String readBody(InputStream in, int bodyLength) throws IOException, RequestException { byte[] buf = new byte[bodyLength]; int pos = 0; while (pos < bodyLength) { int l = in.read(buf, pos, Math.min(512, bodyLength - pos)); if (l < 0) { break; } if (l == 0) { continue; } pos += l; } // Only allow partial return new String(buf, 0, pos, SentinelConfig.charset()); } /** * Consume all the body submitted and parse params into {@link CommandRequest} * * @param queryString * @param request */ protected static void parseParams(String queryString, CommandRequest request) { if (queryString == null || queryString.length() < 1) { return; } int offset = 0, pos = -1; // check anchor queryString = removeAnchor(queryString); while (true) { offset = pos + 1; pos = queryString.indexOf('&', offset); if (offset == pos) { // empty continue; } parseSingleParam(queryString.substring(offset, pos == -1 ? queryString.length() : pos), request); if (pos < 0) { // reach the end break; } } } private void closeResource(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { CommandCenterLog.warn("[SimpleHttpCommandCenter] Close resource failed", e); } } } private void handleResponse(CommandResponse response, final PrintWriter printWriter) throws Exception { if (response.isSuccess()) { if (response.getResult() == null) { writeResponse(printWriter, StatusCode.OK, null); return; } // Here we directly use `toString` to encode the result to plain text. byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset()); writeResponse(printWriter, StatusCode.OK, new String(buffer)); } else { String msg = SERVER_ERROR_MESSAGE; if (response.getException() != null) { msg = response.getException().getMessage(); } writeResponse(printWriter, StatusCode.BAD_REQUEST, msg); } } private void writeResponse(PrintWriter out, StatusCode statusCode, String message) { out.print("HTTP/1.0 " + statusCode.toString() + "\r\n" + "Content-Length: " + (message == null ? 0 : message.getBytes().length) + "\r\n" + "Connection: close\r\n\r\n"); if (message != null) { out.print(message); } out.flush(); writtenHead = true; } /** * Parse raw HTTP request line to a {@link CommandRequest}. * * @param line HTTP request line * @return parsed command request */ protected static CommandRequest processQueryString(String line) { CommandRequest request = new CommandRequest(); if (StringUtil.isBlank(line)) { return request; } int start = line.indexOf('/'); int ask = line.indexOf('?') == -1 ? line.lastIndexOf(' ') : line.indexOf('?'); int space = line.lastIndexOf(' '); String target = line.substring(start != -1 ? start + 1 : 0, ask != -1 ? ask : line.length()); request.addMetadata(HttpCommandUtils.REQUEST_TARGET, target); if (ask == -1 || ask == space) { return request; } String parameterStr = line.substring(ask != -1 ? ask + 1 : 0, space != -1 ? space : line.length()); parseParams(parameterStr, request); return request; } /** * Truncate query from "a=1&b=2#mark" to "a=1&b=2" * * @param str * @return */ protected static String removeAnchor(String str) { if (str == null || str.length() == 0) { return str; } int anchor = str.indexOf('#'); if (anchor == 0) { return ""; } else if (anchor > 0) { return str.substring(0, anchor); } return str; } protected static void parseSingleParam(String single, CommandRequest request) { if (single == null || single.length() < 3) { return; } int index = single.indexOf('='); if (index <= 0 || index >= single.length() - 1) { // empty key/val or nothing found return; } String value = StringUtil.trim(single.substring(index + 1)); String key = StringUtil.trim(single.substring(0, index)); try { key = URLDecoder.decode(key, SentinelConfig.charset()); value = URLDecoder.decode(value, SentinelConfig.charset()); } catch (UnsupportedEncodingException e) { } request.addParam(key, value); } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.http; /** * @author Jason Joo */ public enum StatusCode { /** * 200 OK. */ OK(200, "OK"), BAD_REQUEST(400, "Bad Request"), REQUEST_TIMEOUT(408, "Request Timeout"), LENGTH_REQUIRED(411, "Length Required"), UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), INTERNAL_SERVER_ERROR(500, "Internal Server Error"); private int code; private String desc; private String representation; StatusCode(int code, String desc) { this.code = code; this.desc = desc; this.representation = code + " " + desc; } public int getCode() { return code; } public String getDesc() { return desc; } @Override public String toString() { return representation; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HeartbeatMessage.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.csp.sentinel.util.TimeUtil; /** * Heart beat message entity. * The message consists of key-value pair parameters. * * @author leyou */ public class HeartbeatMessage { private final Map message = new HashMap(); public HeartbeatMessage() { message.put("hostname", HostNameUtil.getHostName()); message.put("ip", TransportConfig.getHeartbeatClientIp()); message.put("app", AppNameUtil.getAppName()); // Put application type (since 1.6.0). message.put("app_type", String.valueOf(SentinelConfig.getAppType())); message.put("port", String.valueOf(TransportConfig.getPort())); } public HeartbeatMessage registerInformation(String key, String value) { message.put(key, value); return this; } public Map generateCurrentMessage() { // Version of Sentinel. message.put("v", Constants.SENTINEL_VERSION); // Actually timestamp. message.put("version", String.valueOf(TimeUtil.currentTimeMillis())); message.put("port", String.valueOf(TransportConfig.getPort())); return message; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/SimpleHttpHeartbeatSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpClient; import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpRequest; import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpResponse; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import java.util.List; /** * The heartbeat sender provides basic API for sending heartbeat request to provided target. * This implementation is based on a trivial HTTP client. * * @author Eric Zhao * @author Carpenter Lee * @author Leo Li */ public class SimpleHttpHeartbeatSender implements HeartbeatSender { private static final int OK_STATUS = 200; private static final long DEFAULT_INTERVAL = 1000 * 10; private final HeartbeatMessage heartBeat = new HeartbeatMessage(); private final SimpleHttpClient httpClient = new SimpleHttpClient(); private final List addressList; private int currentAddressIdx = 0; public SimpleHttpHeartbeatSender() { // Retrieve the list of default addresses. List newAddrs = TransportConfig.getConsoleServerList(); if (newAddrs.isEmpty()) { RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured or not available"); } else { RecordLog.info("[SimpleHttpHeartbeatSender] Default console address list retrieved: {}", newAddrs); } this.addressList = newAddrs; } @Override public boolean sendHeartbeat() throws Exception { if (TransportConfig.getRuntimePort() <= 0) { RecordLog.info("[SimpleHttpHeartbeatSender] Command server port not initialized, won't send heartbeat"); return false; } Endpoint addrInfo = getAvailableAddress(); if (addrInfo == null) { return false; } SimpleHttpRequest request = new SimpleHttpRequest(addrInfo, TransportConfig.getHeartbeatApiPath()); request.setParams(heartBeat.generateCurrentMessage()); try { SimpleHttpResponse response = httpClient.post(request); if (response.getStatusCode() == OK_STATUS) { return true; } else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) { RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo + ", http status code: " + response.getStatusCode()); } } catch (Exception e) { RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo, e); } return false; } @Override public long intervalMs() { return DEFAULT_INTERVAL; } private Endpoint getAvailableAddress() { if (addressList == null || addressList.isEmpty()) { return null; } if (currentAddressIdx < 0) { currentAddressIdx = 0; } int index = currentAddressIdx % addressList.size(); return addressList.get(index); } private boolean clientErrorCode(int code) { return code > 399 && code < 500; } private boolean serverErrorCode(int code) { return code > 499 && code < 600; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/SimpleHttpClient.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat.client; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.Map; import java.util.Map.Entry; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; /** *

          * A very simple HTTP client that only supports GET/POST method and plain text request body. * The Content-Type header is always set as

          application/x-www-form-urlencoded
          . * All parameters in the request will be encoded using {@link URLEncoder#encode(String, String)}. *

          *

          * The result of a HTTP invocation will be wrapped as a {@link SimpleHttpResponse}. Content in response body * will be automatically decoded to string with provided charset. *

          *

          * This is a blocking and synchronous client, so an invocation will await the response until timeout exceed. *

          *

          * Note that this is a very NAIVE client, {@code Content-Length} must be specified in the * HTTP response header, otherwise, the response body will be dropped. All other body type such as * {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported. *

          * * @author leyou * @author Leo Li */ public class SimpleHttpClient { /** * Execute a GET HTTP request. * * @param request HTTP request * @return the response if the request is successful * @throws IOException when connection cannot be established or the connection is interrupted */ public SimpleHttpResponse get(SimpleHttpRequest request) throws IOException { if (request == null) { return null; } return request(request.getEndpoint(), RequestMethod.GET, request.getRequestPath(), request.getParams(), request.getCharset(), request.getSoTimeout()); } /** * Execute a POST HTTP request. * * @param request HTTP request * @return the response if the request is successful * @throws IOException when connection cannot be established or the connection is interrupted */ public SimpleHttpResponse post(SimpleHttpRequest request) throws IOException { if (request == null) { return null; } return request(request.getEndpoint(), RequestMethod.POST, request.getRequestPath(), request.getParams(), request.getCharset(), request.getSoTimeout()); } private SimpleHttpResponse request(Endpoint endpoint, RequestMethod type, String requestPath, Map paramsMap, Charset charset, int soTimeout) throws IOException { Socket socket = null; BufferedWriter writer; InetSocketAddress socketAddress = new InetSocketAddress(endpoint.getHost(), endpoint.getPort()); try { socket = SocketFactory.getSocket(endpoint.getProtocol()); socket.setSoTimeout(soTimeout); socket.connect(socketAddress, soTimeout); writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset)); requestPath = getRequestPath(type, requestPath, paramsMap, charset); writer.write(getStatusLine(type, requestPath) + "\r\n"); if (charset != null) { writer.write("Content-Type: application/x-www-form-urlencoded; charset=" + charset.name() + "\r\n"); } else { writer.write("Content-Type: application/x-www-form-urlencoded\r\n"); } writer.write("Host: " + socketAddress.getHostName() + "\r\n"); if (type == RequestMethod.GET) { writer.write("Content-Length: 0\r\n"); writer.write("\r\n"); } else { // POST method. String params = encodeRequestParams(paramsMap, charset); writer.write("Content-Length: " + params.getBytes(charset).length + "\r\n"); writer.write("\r\n"); writer.write(params); } writer.flush(); SimpleHttpResponse response = new SimpleHttpResponseParser().parse(socket.getInputStream()); socket.close(); socket = null; return response; } finally { if (socket != null) { try { socket.close(); } catch (Exception ex) { RecordLog.warn("Error when closing {} request to {} in SimpleHttpClient", type, socketAddress, ex); } } } } private String getRequestPath(RequestMethod type, String requestPath, Map paramsMap, Charset charset) { if (type == RequestMethod.GET) { if (requestPath.contains("?")) { return requestPath + "&" + encodeRequestParams(paramsMap, charset); } return requestPath + "?" + encodeRequestParams(paramsMap, charset); } return requestPath; } private String getStatusLine(RequestMethod type, String requestPath) { if (type == RequestMethod.POST) { return "POST " + requestPath + " HTTP/1.1"; } return "GET " + requestPath + " HTTP/1.1"; } /** * Encode and get the URL request parameters. * * @param paramsMap pair of parameters * @param charset charset * @return encoded request parameters, or empty string ("") if no parameters are provided */ private String encodeRequestParams(Map paramsMap, Charset charset) { if (charset == null) { throw new IllegalArgumentException("charset is not allowed to be null"); } if (paramsMap == null || paramsMap.isEmpty()) { return ""; } try { StringBuilder paramsBuilder = new StringBuilder(); for (Entry entry : paramsMap.entrySet()) { if (entry.getKey() == null || entry.getValue() == null) { continue; } paramsBuilder.append(URLEncoder.encode(entry.getKey(), charset.name())) .append("=") .append(URLEncoder.encode(entry.getValue(), charset.name())) .append("&"); } if (paramsBuilder.length() > 0) { // Remove the last '&'. paramsBuilder.delete(paramsBuilder.length() - 1, paramsBuilder.length()); } return paramsBuilder.toString(); } catch (Throwable e) { RecordLog.warn("Encode request params fail", e); return ""; } } private enum RequestMethod { GET, POST } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/SimpleHttpRequest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat.client; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; /** * Simple HTTP request representation. * * @author leyou * @author Leo Li */ public class SimpleHttpRequest { private Endpoint endpoint; private String requestPath = ""; private int soTimeout = 3000; private Map params; private Charset charset = Charset.forName(SentinelConfig.charset()); public SimpleHttpRequest(Endpoint endpoint, String requestPath) { this.endpoint = endpoint; this.requestPath = requestPath; } public Endpoint getEndpoint() { return endpoint; } public SimpleHttpRequest setEndpoint(Endpoint endpoint) { this.endpoint = endpoint; return this; } public String getRequestPath() { return requestPath; } public SimpleHttpRequest setRequestPath(String requestPath) { this.requestPath = requestPath; return this; } public int getSoTimeout() { return soTimeout; } public SimpleHttpRequest setSoTimeout(int soTimeout) { this.soTimeout = soTimeout; return this; } public Map getParams() { return params; } public SimpleHttpRequest setParams(Map params) { this.params = params; return this; } public Charset getCharset() { return charset; } public SimpleHttpRequest setCharset(Charset charset) { this.charset = charset; return this; } public SimpleHttpRequest addParam(String key, String value) { if (StringUtil.isBlank(key)) { throw new IllegalArgumentException("Parameter key cannot be empty"); } if (params == null) { params = new HashMap(); } params.put(key, value); return this; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/SimpleHttpResponse.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat.client; import com.alibaba.csp.sentinel.config.SentinelConfig; import java.nio.charset.Charset; import java.util.Map; /** * Simple HTTP response representation. * * @author leyou */ public class SimpleHttpResponse { private Charset charset = Charset.forName(SentinelConfig.charset()); private String statusLine; private int statusCode; private Map headers; private byte[] body; public SimpleHttpResponse(String statusLine, Map headers) { this.statusLine = statusLine; this.headers = headers; } public SimpleHttpResponse(String statusLine, Map headers, byte[] body) { this.statusLine = statusLine; this.headers = headers; this.body = body; } private void parseCharset() { String contentType = getHeader("Content-Type"); for (String str : contentType.split(" ")) { if (str.toLowerCase().startsWith("charset=")) { charset = Charset.forName(str.split("=")[1]); } } } private void parseCode() { this.statusCode = Integer.parseInt(statusLine.split(" ")[1]); } public void setBody(byte[] body) { this.body = body; } public byte[] getBody() { return body; } public String getStatusLine() { return statusLine; } public Integer getStatusCode() { if (statusCode == 0) { parseCode(); } return statusCode; } public Map getHeaders() { return headers; } /** * Get header of the key ignoring case. * * @param key header key * @return header value */ public String getHeader(String key) { if (headers == null) { return null; } String value = headers.get(key); if (value != null) { return value; } for (Map.Entry entry : headers.entrySet()) { if (entry.getKey().equalsIgnoreCase(key)) { return entry.getValue(); } } return null; } public String getBodyAsString() { parseCharset(); return new String(body, charset); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(statusLine) .append("\r\n"); if (headers != null) { for (Map.Entry entry : headers.entrySet()) { buf.append(entry.getKey()).append(": ").append(entry.getValue()) .append("\r\n"); } } buf.append("\r\n"); buf.append(getBodyAsString()); return buf.toString(); } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/SimpleHttpResponseParser.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat.client; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** *

          * The parser provides functionality to parse raw bytes HTTP response to a {@link SimpleHttpResponse}. *

          *

          * Note that this is a very NAIVE parser, {@code Content-Length} must be specified in the * HTTP response header, otherwise, the body will be dropped. All other body type such as * {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported. *

          * * @author leyou */ public class SimpleHttpResponseParser { private static final int MAX_BODY_SIZE = 1024 * 1024 * 4; private byte[] buf; public SimpleHttpResponseParser(int maxSize) { if (maxSize < 0) { throw new IllegalArgumentException("maxSize must > 0"); } this.buf = new byte[maxSize]; } public SimpleHttpResponseParser() { this(1024 * 4); } /** * Parse bytes from an input stream to a {@link SimpleHttpResponse}. * * @param in input stream * @return parsed HTTP response entity * @throws IOException when an IO error occurs */ public SimpleHttpResponse parse(InputStream in) throws IOException { int bg = 0; int len; String statusLine = null; Map headers = new HashMap(); Charset charset = Charset.forName("utf-8"); int contentLength = -1; SimpleHttpResponse response; while (true) { if (bg >= buf.length) { throw new IndexOutOfBoundsException("buf index out of range: " + bg + ", buf.length=" + buf.length); } if ((len = in.read(buf, bg, buf.length - bg)) > 0) { bg += len; len = bg; int idx; int parseBg = 0; while ((idx = indexOfCRLF(parseBg, len)) >= 0) { String line = new String(buf, parseBg, idx - parseBg, charset); parseBg = idx + 2; if (statusLine == null) { statusLine = line; } else { if (line.isEmpty()) { //When the `Content-Length` is absent, parse the rest of the bytes as body directly. //if (contentLength == -1) { // contentLength = MAX_BODY_SIZE; //} // Parse HTTP body. // When the `Content-Length` is absent, drop the body, return directly. response = new SimpleHttpResponse(statusLine, headers); if (contentLength <= 0) { return response; } ByteArrayOutputStream out = new ByteArrayOutputStream(1024); // `Content-Length` is not equal to exact length. if (contentLength < len - parseBg) { throw new IllegalStateException("Invalid content length: " + contentLength); } out.write(buf, parseBg, len - parseBg); if (out.size() > MAX_BODY_SIZE) { throw new IllegalStateException( "Request body is too big, limit size is " + MAX_BODY_SIZE); } int cap = Math.min(contentLength - out.size(), buf.length); while (cap > 0 && (len = in.read(buf, 0, cap)) > 0) { out.write(buf, 0, len); cap = Math.min(contentLength - out.size(), buf.length); } response.setBody(out.toByteArray()); return response; } else if (!line.trim().isEmpty()) { // Parse HTTP header. int idx2 = line.indexOf(":"); String key = line.substring(0, idx2).trim(); String value = line.substring(idx2 + 1).trim(); headers.put(key, value); if ("Content-Length".equalsIgnoreCase(key)) { contentLength = Integer.parseInt(value); } } } } // Move remaining bytes to the beginning. if (parseBg != 0) { System.arraycopy(buf, parseBg, buf, 0, len - parseBg); } bg = len - parseBg; } else { break; } } return null; } /** * Get the index of CRLF separator. * * @param bg begin offset * @param ed end offset * @return the index, or {@code -1} if no CRLF is found */ private int indexOfCRLF(int bg, int ed) { if (ed - bg < 2) { return -1; } for (int i = bg; i < ed - 1; i++) { if (buf[i] == '\r' && buf[i + 1] == '\n') { return i; } } return -1; } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/SocketFactory.java ================================================ package com.alibaba.csp.sentinel.transport.heartbeat.client; import java.io.IOException; import java.net.Socket; import javax.net.ssl.SSLSocketFactory; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import com.alibaba.csp.sentinel.transport.ssl.SslFactory; /** * @author Leo Li */ public class SocketFactory { private static class SSLSocketFactoryInstance { private static final SSLSocketFactory SSL_SOCKET_FACTORY = SslFactory.getSslConnectionSocketFactory().getSocketFactory(); } public static Socket getSocket(Protocol protocol) throws IOException { return protocol == Protocol.HTTP ? new Socket() : SSLSocketFactoryInstance.SSL_SOCKET_FACTORY.createSocket(); } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter ================================================ com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender ================================================ com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/test/java/com/alibaba/csp/sentinel/transport/command/http/CommandCenterTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.http; import com.alibaba.csp.sentinel.command.CommandCenterProvider; import com.alibaba.csp.sentinel.transport.CommandCenter; import org.junit.Assert; import org.junit.Test; public class CommandCenterTest { @Test public void stopCommandCenter(){ CommandCenter commandCenter = CommandCenterProvider.getCommandCenter(); try { commandCenter.stop(); } catch (Exception e) { Assert.fail(); } } @Test public void startCommandCenter(){ CommandCenter commandCenter = CommandCenterProvider.getCommandCenter(); try { commandCenter.start(); } catch (Exception e) { Assert.fail(); } } @Test public void beforeStartCommandCenter(){ CommandCenter commandCenter = CommandCenterProvider.getCommandCenter(); try { commandCenter.beforeStart(); } catch (Exception e) { Assert.fail(); } } } ================================================ FILE: sentinel-transport/sentinel-transport-simple-http/src/test/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTaskTest.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.Map; import org.junit.Test; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.transport.command.exception.RequestException; public class HttpEventTaskTest { @Test public void processQueryString() { CommandRequest request; request = HttpEventTask.processQueryString(null); assertNotNull(request); request = HttpEventTask.processQueryString(null); assertNotNull(request); request = HttpEventTask.processQueryString("get /?a=1&b=2&c=3#mark HTTP/1.0"); assertNotNull(request); assertEquals("1", request.getParam("a")); assertEquals("2", request.getParam("b")); assertEquals("3", request.getParam("c")); request = HttpEventTask.processQueryString("post /test?a=3&b=4&c=3#mark HTTP/1.0"); assertNotNull(request); assertEquals("3", request.getParam("a")); assertEquals("4", request.getParam("b")); assertEquals("3", request.getParam("c")); } @Test public void removeAnchor() { assertNull(HttpEventTask.removeAnchor(null)); assertEquals("", HttpEventTask.removeAnchor("")); assertEquals("", HttpEventTask.removeAnchor("#mark")); assertEquals("a", HttpEventTask.removeAnchor("a#mark")); } @Test public void parseSingleParam() { CommandRequest request; request = new CommandRequest(); HttpEventTask.parseSingleParam(null, request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("a", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("=", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("a=", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("=a", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("test=", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("=test", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseSingleParam("a=1", request); assertEquals(1, request.getParameters().size()); assertEquals("1", request.getParam("a")); request = new CommandRequest(); HttpEventTask.parseSingleParam("a_+=1+", request); assertEquals(1, request.getParameters().size()); assertEquals("1 ", request.getParam("a_ ")); } @Test public void parseParams() { CommandRequest request; // mixed request = new CommandRequest(); HttpEventTask.parseParams("a=1&&b&=3&&c=4&a_+1=3_3%20&%E7%9A%84=test%E7%9A%84#mark", request); assertEquals(4, request.getParameters().size()); assertEquals("1", request.getParam("a")); assertNull(request.getParam("b")); assertEquals("4", request.getParam("c")); assertEquals("3_3 ", request.getParam("a_ 1")); assertEquals("test的", request.getParam("的")); request = new CommandRequest(); HttpEventTask.parseParams(null, request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseParams("", request); assertEquals(0, request.getParameters().size()); request = new CommandRequest(); HttpEventTask.parseParams("&&b&=3&", request); assertEquals(0, request.getParameters().size()); } @Test public void parsePostHeaders() throws IOException { Map map; map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("".getBytes())); assertTrue(map.size() == 0); map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-type: test \r\n\r\nbody".getBytes())); assertEquals("test", map.get("content-type")); map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-Encoding: utf-8\r\n\r\nbody".getBytes())); assertEquals("utf-8", map.get("content-encoding")); } @Test public void processPostRequest() throws IOException { CommandRequest request; request = new CommandRequest(); request.addParam("a", "1"); // illegal(empty) request try { HttpEventTask.processPostRequest(new ByteArrayInputStream("".getBytes()), request); assertFalse(true); // should not reach here } catch (Exception e) { assertTrue(e instanceof RequestException); } assertEquals("1", request.getParam("a")); // normal request try { HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "Accept-Encoding: gzip, deflate\r\n" + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 10\r\n" + "\r\n" + "a=3&b=5的").getBytes()), request); assertEquals("3", request.getParam("a")); assertEquals("5的", request.getParam("b")); } catch (Exception e) { assertTrue(false); // should not reach here } // not supported request try { HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "Accept-Encoding: gzip, deflate\r\n" + "Content-Type: application/json\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 7\r\n" + "\r\n" + "a=1&b=2").getBytes()), request); assertTrue(false); // should not reach here } catch (RequestException e) { assertTrue(e.getStatusCode() == StatusCode.UNSUPPORTED_MEDIA_TYPE); } // Capacity test char[] buf = new char[1024 * 1024]; Arrays.fill(buf, '&'); String padding = new String(buf); try { request = new CommandRequest(); HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "Accept-Encoding: gzip, deflate\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 7\r\n" + "\r\n" + padding + "a=1&b=2").getBytes()), request); assertEquals(0, request.getParameters().size()); } catch (Exception e) { assertTrue(false); } try { String querystring = "a+=+&b=%E7%9A%84的"; request = new CommandRequest(); HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "Accept-Encoding: gzip, deflate\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: keep-alive\r\n" + "Content-Length: " + (padding.length() + querystring.getBytes().length) + "\r\n" + "\r\n" + padding + querystring).getBytes()), request); assertEquals(2, request.getParameters().size()); assertEquals(" ", request.getParam("a ")); assertEquals("的的", request.getParam("b")); } catch (Exception e) { assertTrue(false); } } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/pom.xml ================================================ com.alibaba.csp sentinel-transport ${revision} ../pom.xml 4.0.0 ${project.groupId}:${project.artifactId} sentinel-transport-spring-mvc 4.5.13 3.1.0 5.3.18 com.alibaba.csp sentinel-transport-common ${project.version} junit junit test org.springframework spring-webmvc ${spring.version} provided javax.servlet javax.servlet-api ${servlet.api.version} provided org.apache.httpcomponents httpclient ${apache.httpclient.version} ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandler.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandRequest; import com.alibaba.csp.sentinel.command.CommandResponse; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.transport.command.http.StatusCode; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.Map; /** * @author shenbaoyong */ public class SentinelApiHandler { public static final String SERVER_ERROR_MESSAGE = "Command server error"; private CommandHandler commandHandler; public SentinelApiHandler(CommandHandler commandHandler) { this.commandHandler = commandHandler; } public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { PrintWriter printWriter = null; try { long start = System.currentTimeMillis(); printWriter = httpServletResponse.getWriter(); CommandCenterLog.debug("[SentinelApiHandler] request income: {}", httpServletRequest.getRequestURL()); CommandRequest request = new CommandRequest(); Map parameterMap = httpServletRequest.getParameterMap(); for (Map.Entry entry : parameterMap.entrySet()) { String[] value = entry.getValue(); if (value != null && value.length >= 1) { request.addParam(entry.getKey(), value[0]); } } CommandResponse response = commandHandler.handle(request); handleResponse(response, httpServletResponse, printWriter); long cost = System.currentTimeMillis() - start; CommandCenterLog.debug("[SentinelApiHandler] Deal request: {}, time cost: {} ms", httpServletRequest.getRequestURL(), cost); } catch (Throwable e) { CommandCenterLog.warn("[SentinelApiHandler] error", e); try { if (printWriter != null) { writeResponse(httpServletResponse, printWriter, StatusCode.INTERNAL_SERVER_ERROR, SERVER_ERROR_MESSAGE); } } catch (Exception e1) { CommandCenterLog.warn("Failed to write error response", e1); } } } private void writeResponse(HttpServletResponse httpServletResponse, PrintWriter out, StatusCode statusCode, String message) { httpServletResponse.setStatus(statusCode.getCode()); if (message != null) { out.print(message); } out.flush(); } private void handleResponse(CommandResponse response, HttpServletResponse httpServletResponse, final PrintWriter printWriter) throws Exception { if (response.isSuccess()) { if (response.getResult() == null) { writeResponse(httpServletResponse, printWriter, StatusCode.OK, null); return; } // Here we directly use `toString` to encode the result to plain text. byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset()); writeResponse(httpServletResponse, printWriter, StatusCode.OK, new String(buffer)); } else { String msg = SERVER_ERROR_MESSAGE; if (response.getException() != null) { msg = response.getException().getMessage(); } writeResponse(httpServletResponse, printWriter, StatusCode.BAD_REQUEST, msg); } } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerAdapter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import org.springframework.core.Ordered; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author shenbaoyong */ public class SentinelApiHandlerAdapter implements HandlerAdapter, Ordered { private int order = Ordered.LOWEST_PRECEDENCE; public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return order; } @Override public boolean supports(Object handler) { return handler instanceof SentinelApiHandler; } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { SentinelApiHandler sentinelApiHandler = (SentinelApiHandler) handler; sentinelApiHandler.handle(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { return -1; } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerMapping.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.log.CommandCenterLog; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.util.ClassUtils; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.AbstractHandlerMapping; import javax.servlet.http.HttpServletRequest; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author shenbaoyong */ public class SentinelApiHandlerMapping extends AbstractHandlerMapping implements ApplicationListener { private static final String SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS = "org.springframework.boot.web.context.WebServerInitializedEvent"; private static Class webServerInitializedEventClass; static { try { webServerInitializedEventClass = ClassUtils.forName(SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS, null); RecordLog.info("[SentinelApiHandlerMapping] class {} is present, this is a spring-boot app, we can auto detect port", SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS); } catch (ClassNotFoundException e) { RecordLog.info("[SentinelApiHandlerMapping] class {} is not present, this is not a spring-boot app, we can not auto detect port", SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS); } } final static Map handlerMap = new ConcurrentHashMap<>(); private boolean ignoreInterceptor = true; public SentinelApiHandlerMapping() { setOrder(Ordered.LOWEST_PRECEDENCE - 10); } @Override protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String commandName = request.getRequestURI(); if (commandName.startsWith("/")) { commandName = commandName.substring(1); } CommandHandler commandHandler = handlerMap.get(commandName); return commandHandler != null ? new SentinelApiHandler(commandHandler) : null; } @Override protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { return ignoreInterceptor ? new HandlerExecutionChain(handler) : super.getHandlerExecutionChain(handler, request); } public void setIgnoreInterceptor(boolean ignoreInterceptor) { this.ignoreInterceptor = ignoreInterceptor; } public static void registerCommand(String commandName, CommandHandler handler) { if (StringUtil.isEmpty(commandName) || handler == null) { return; } if (handlerMap.containsKey(commandName)) { CommandCenterLog.warn("[SentinelApiHandlerMapping] Register failed (duplicate command): " + commandName); return; } handlerMap.put(commandName, handler); } public static void registerCommands(Map handlerMap) { if (handlerMap != null) { for (Map.Entry e : handlerMap.entrySet()) { registerCommand(e.getKey(), e.getValue()); } } } @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { if (webServerInitializedEventClass != null && webServerInitializedEventClass.isAssignableFrom(applicationEvent.getClass())) { Integer port = null; try { BeanWrapper beanWrapper = new BeanWrapperImpl(applicationEvent); port = (Integer) beanWrapper.getPropertyValue("webServer.port"); } catch (Exception e) { RecordLog.warn("[SentinelApiHandlerMapping] resolve port from event " + applicationEvent + " fail", e); } if (port != null && TransportConfig.getPort() == null) { RecordLog.info("[SentinelApiHandlerMapping] resolve port {} from event {}", port, applicationEvent); TransportConfig.setRuntimePort(port); } } } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SpringMvcHttpCommandCenter.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command; import com.alibaba.csp.sentinel.command.CommandHandler; import com.alibaba.csp.sentinel.command.CommandHandlerProvider; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.transport.CommandCenter; import java.util.Map; /** * @author shenbaoyong */ @Spi(order = Spi.ORDER_LOWEST - 100) public class SpringMvcHttpCommandCenter implements CommandCenter { @Override public void start() throws Exception { } @Override public void stop() throws Exception { } @Override public void beforeStart() throws Exception { // Register handlers Map handlers = CommandHandlerProvider.getInstance().namedHandlers(); SentinelApiHandlerMapping.registerCommands(handlers); } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.command.http; /** * @author Jason Joo */ public enum StatusCode { /** * 200 OK. */ OK(200, "OK"), BAD_REQUEST(400, "Bad Request"), REQUEST_TIMEOUT(408, "Request Timeout"), LENGTH_REQUIRED(411, "Length Required"), UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), INTERNAL_SERVER_ERROR(500, "Internal Server Error"); private int code; private String desc; private String representation; StatusCode(int code, String desc) { this.code = code; this.desc = desc; this.representation = code + " " + desc; } public int getCode() { return code; } public String getDesc() { return desc; } @Override public String toString() { return representation; } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/SpringMvcHttpHeartbeatSender.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.spi.Spi; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import com.alibaba.csp.sentinel.transport.heartbeat.client.HttpClientsFactory; import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.HostNameUtil; import com.alibaba.csp.sentinel.util.PidUtil; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import java.util.List; /** * @author Eric Zhao * @author Carpenter Lee * @author Leo Li */ @Spi(order = Spi.ORDER_LOWEST - 100) public class SpringMvcHttpHeartbeatSender implements HeartbeatSender { private final CloseableHttpClient client; private static final int OK_STATUS = 200; private final int timeoutMs = 3000; private final RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(timeoutMs) .setConnectTimeout(timeoutMs) .setSocketTimeout(timeoutMs) .build(); private final Protocol consoleProtocol; private final String consoleHost; private final int consolePort; public SpringMvcHttpHeartbeatSender() { List dashboardList = TransportConfig.getConsoleServerList(); if (dashboardList == null || dashboardList.isEmpty()) { RecordLog.info("[HttpHeartbeatSender] No dashboard server available"); consoleProtocol = Protocol.HTTP; consoleHost = null; consolePort = -1; } else { consoleProtocol = dashboardList.get(0).getProtocol(); consoleHost = dashboardList.get(0).getHost(); consolePort = dashboardList.get(0).getPort(); RecordLog.info("[HttpHeartbeatSender] Dashboard address parsed: <{}:{}>", consoleHost, consolePort); } this.client = HttpClientsFactory.getHttpClientsByProtocol(consoleProtocol); } @Override public boolean sendHeartbeat() throws Exception { if (StringUtil.isEmpty(consoleHost)) { return false; } URIBuilder uriBuilder = new URIBuilder(); uriBuilder.setScheme(consoleProtocol.getProtocol()).setHost(consoleHost).setPort(consolePort) .setPath(TransportConfig.getHeartbeatApiPath()) .setParameter("app", AppNameUtil.getAppName()) .setParameter("app_type", String.valueOf(SentinelConfig.getAppType())) .setParameter("v", Constants.SENTINEL_VERSION) .setParameter("version", String.valueOf(System.currentTimeMillis())) .setParameter("hostname", HostNameUtil.getHostName()) .setParameter("ip", TransportConfig.getHeartbeatClientIp()) .setParameter("port", TransportConfig.getPort()) .setParameter("pid", String.valueOf(PidUtil.getPid())); HttpGet request = new HttpGet(uriBuilder.build()); request.setConfig(requestConfig); // Send heartbeat request. CloseableHttpResponse response = client.execute(request); response.close(); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == OK_STATUS) { return true; } else if (clientErrorCode(statusCode) || serverErrorCode(statusCode)) { RecordLog.warn("[HttpHeartbeatSender] Failed to send heartbeat to " + consoleHost + ":" + consolePort + ", http status code: " + statusCode); } return false; } @Override public long intervalMs() { return 5000; } private boolean clientErrorCode(int code) { return code > 399 && code < 500; } private boolean serverErrorCode(int code) { return code > 499 && code < 600; } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/HttpClientsFactory.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.transport.heartbeat.client; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import com.alibaba.csp.sentinel.transport.ssl.SslFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; /** * @author Leo Li */ public class HttpClientsFactory { private static class SslConnectionSocketFactoryInstance { private static final SSLConnectionSocketFactory SSL_CONNECTION_SOCKET_FACTORY = new SSLConnectionSocketFactory(SslFactory.getSslConnectionSocketFactory(), NoopHostnameVerifier.INSTANCE); } public static CloseableHttpClient getHttpClientsByProtocol(Protocol protocol) { return protocol == Protocol.HTTP ? HttpClients.createDefault() : HttpClients.custom(). setSSLSocketFactory(SslConnectionSocketFactoryInstance.SSL_CONNECTION_SOCKET_FACTORY).build(); } } ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter ================================================ com.alibaba.csp.sentinel.transport.command.SpringMvcHttpCommandCenter ================================================ FILE: sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender ================================================ com.alibaba.csp.sentinel.transport.heartbeat.SpringMvcHttpHeartbeatSender ================================================ FILE: toolchains-example.xml ================================================ jdk 8 temurin temurin_8 /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home jdk 11 temurin temurin_11 /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home jdk 17 temurin temurin_17 /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home jdk 21 temurin temurin_21 /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home