├── gradle.properties ├── _config.yml ├── .gitignore ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── test │ ├── resources │ │ ├── scripts │ │ │ └── start_kafka.sh │ │ ├── logback.xml │ │ ├── docker-compose-integrationtest.yml │ │ └── docker-compose-kafkafailover-integrationtest.yml │ ├── java │ │ └── com │ │ │ ├── sixt │ │ │ └── service │ │ │ │ ├── testrpcclasses │ │ │ │ ├── TestService1.java │ │ │ │ ├── TestService2.java │ │ │ │ ├── package1 │ │ │ │ │ └── TestService.java │ │ │ │ └── package2 │ │ │ │ │ └── TestService.java │ │ │ │ └── framework │ │ │ │ ├── rpc │ │ │ │ ├── RpcCallExceptionDecoderTest.java │ │ │ │ ├── backoff │ │ │ │ │ └── ExponentialRetryBackOffTest.java │ │ │ │ ├── ProtobufRpcCallExceptionDecoderTest.java │ │ │ │ └── ServiceEndpointListTest.java │ │ │ │ ├── kafka │ │ │ │ ├── QueueTypeEnumTest.java │ │ │ │ ├── KafkaSubscriberJsonFactoryTest.java │ │ │ │ ├── KafkaPublisherTest.java │ │ │ │ ├── EagerMessageQueueTest.java │ │ │ │ └── KafkaSubscriberProtobufFactoryTest.java │ │ │ │ ├── IntegrationTest.java │ │ │ │ ├── metrics │ │ │ │ └── MetricBuilderTest.java │ │ │ │ ├── servicetest │ │ │ │ ├── mockservice │ │ │ │ │ ├── ServiceImpersonatorLoadBalancerTest.java │ │ │ │ │ └── ImpersonatedPortDictionaryTest.java │ │ │ │ ├── helper │ │ │ │ │ └── DockerPortResolverTest.java │ │ │ │ └── eventing │ │ │ │ │ └── EventUtilsTest.java │ │ │ │ ├── injection │ │ │ │ ├── InjectionModuleTest.java │ │ │ │ └── OrangeServletModuleTest.java │ │ │ │ ├── util │ │ │ │ └── FileUtilTest.java │ │ │ │ ├── registry │ │ │ │ └── consul │ │ │ │ │ └── ConsulHealthEntryFactoryTest.java │ │ │ │ ├── AbstractServiceTest.java │ │ │ │ ├── logging │ │ │ │ └── SixtJsonEncoderTest.java │ │ │ │ ├── json │ │ │ │ ├── JsonUtilTest.java │ │ │ │ └── JsonRpcRequestTest.java │ │ │ │ ├── AbstractServiceTest_initializeGuice.java │ │ │ │ ├── jetty │ │ │ │ └── RpcServletTest.java │ │ │ │ ├── database │ │ │ │ └── SchemaMigratorIntegrationTest.java │ │ │ │ └── protobuf │ │ │ │ └── ProtobufRpcResponseTest.java │ │ │ └── squareup │ │ │ └── wire │ │ │ └── schema │ │ │ └── internal │ │ │ └── parser │ │ │ └── SixtProtoParserTest.java │ └── proto │ │ ├── TestEvent.proto │ │ ├── MessagingTest.proto │ │ └── FrameworkTest.proto └── main │ ├── java │ └── com │ │ ├── sixt │ │ └── service │ │ │ └── framework │ │ │ ├── kafka │ │ │ ├── MessageExecutor.java │ │ │ ├── MessageQueue.java │ │ │ ├── EagerMessageQueue.java │ │ │ ├── SaslConfigurator.java │ │ │ ├── EventReceivedCallback.java │ │ │ ├── messaging │ │ │ │ ├── UnknownMessageTypeException.java │ │ │ │ ├── UnknownMessageHandlerException.java │ │ │ │ ├── RetryDelayer.java │ │ │ │ ├── MessageHandler.java │ │ │ │ ├── Message.java │ │ │ │ ├── FailedMessageProcessor.java │ │ │ │ ├── DiscardFailedMessages.java │ │ │ │ ├── ProducerFactory.java │ │ │ │ ├── PartitionProcessorFactory.java │ │ │ │ ├── SimpleRetryDelayer.java │ │ │ │ ├── MessageType.java │ │ │ │ └── DelayAndRetryOnRecoverableErrors.java │ │ │ ├── KafkaPublisherBuilder.java │ │ │ ├── SixtPartitioner.java │ │ │ └── KafkaTopicInfo.java │ │ │ ├── injection │ │ │ ├── MessagingModule.java │ │ │ ├── OrangeServletModule.java │ │ │ └── ConsumerFactoryProvider.java │ │ │ ├── rpc │ │ │ ├── backoff │ │ │ │ ├── DefaultExponentialBackOff.java │ │ │ │ ├── ExponentialRetryBackOff.java │ │ │ │ └── RetryBackOffFunction.java │ │ │ ├── LoadBalancerUpdate.java │ │ │ ├── RpcClientFactory.java │ │ │ ├── RpcCallExceptionDecoder.java │ │ │ ├── JsonRpcCallExceptionDecoder.java │ │ │ ├── ServiceDependencyHealthCheck.java │ │ │ ├── RpcClientMetrics.java │ │ │ ├── ProtobufRpcCallExceptionDecoder.java │ │ │ └── LoadBalancer.java │ │ │ ├── util │ │ │ ├── FileUtil.java │ │ │ ├── Sleeper.java │ │ │ ├── ProcessUtil.java │ │ │ └── MockMethodHandler.java │ │ │ ├── metrics │ │ │ ├── MetricBuilderFactory.java │ │ │ ├── GaugeGetter.java │ │ │ ├── MetricTag.java │ │ │ ├── MetricsReporterProvider.java │ │ │ ├── GoGauge.java │ │ │ ├── GoTimer.java │ │ │ └── GoCounter.java │ │ │ ├── tracing │ │ │ ├── TracingProvider.java │ │ │ └── NoopTracingProvider.java │ │ │ ├── configuration │ │ │ ├── ChangeCallback.java │ │ │ ├── ConfigurationProvider.java │ │ │ └── LogLevelChangeCallback.java │ │ │ ├── logging │ │ │ ├── LoggingContext.java │ │ │ ├── SixtLogger.java │ │ │ ├── SixtJsonEncoder.java │ │ │ ├── SixtLogbackContext.java │ │ │ └── CustomLogLevelJsonProvider.java │ │ │ ├── ServiceMethodHandler.java │ │ │ ├── annotation │ │ │ ├── HealthCheckProvider.java │ │ │ ├── OrangeMicroservice.java │ │ │ ├── MetricsReporterPlugin.java │ │ │ ├── TracingPlugin.java │ │ │ ├── RpcHandler.java │ │ │ ├── ServiceRegistryPlugin.java │ │ │ ├── ConfigurationPlugin.java │ │ │ └── EnableDatabaseMigration.java │ │ │ ├── registry │ │ │ ├── ServiceRegistrationProvider.java │ │ │ ├── ServiceDiscoveryProvider.java │ │ │ └── consul │ │ │ │ ├── ConsulHealthEntryFilter.java │ │ │ │ └── ConsulServiceRegistryPlugin.java │ │ │ ├── health │ │ │ ├── HealthCheckContributor.java │ │ │ └── ReadinessCheckServer.java │ │ │ ├── ServiceMethodPreHook.java │ │ │ ├── ServiceMethodPostHook.java │ │ │ ├── servicetest │ │ │ ├── eventing │ │ │ │ └── EventUtils.java │ │ │ ├── injection │ │ │ │ ├── ServiceUnderTestModule.java │ │ │ │ └── ServiceImpersonatorModule.java │ │ │ ├── service │ │ │ │ └── ServiceUnderTestLoadBalancer.java │ │ │ └── mockservice │ │ │ │ └── ImpersonatedPortDictionary.java │ │ │ ├── database │ │ │ └── DatabaseMigrationContributor.java │ │ │ ├── jetty │ │ │ └── JettyComposer.java │ │ │ ├── json │ │ │ └── JsonRpcRequest.java │ │ │ └── protobuf │ │ │ └── ProtobufRpcRequest.java │ │ └── squareup │ │ └── wire │ │ └── schema │ │ └── internal │ │ └── parser │ │ └── RpcMethodDefinition.java │ ├── proto │ ├── RpcEnvelope.proto │ ├── Messaging.proto │ └── configuration.proto │ └── resources │ └── logback-base.xml ├── sit ├── src │ ├── serviceTest │ │ └── resources │ │ │ ├── scripts │ │ │ └── start_kafka.sh │ │ │ ├── logback.xml │ │ │ └── docker-compose.yml │ └── main │ │ ├── proto │ │ ├── AnotherService.proto │ │ ├── Messages.proto │ │ └── TestService.proto │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── sixt │ │ └── service │ │ └── test_service │ │ ├── handler │ │ ├── RandomHandlerPreHook.java │ │ ├── RandomHandlerPostHook.java │ │ ├── SlowRespondingHandler.java │ │ ├── RandomHandler.java │ │ └── CallsAnotherServiceHandler.java │ │ ├── messaging │ │ └── MessagingAdaptor.java │ │ ├── infrastructure │ │ └── RandomEventPublisher.java │ │ └── ServiceEntryPoint.java └── docker_clean.sh ├── travis-build.sh ├── .travis.yml ├── .github └── PULL_REQUEST_TEMPLATE.md └── gradlew.bat /gradle.properties: -------------------------------------------------------------------------------- 1 | version=3.3.1 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-leap-day -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | /.idea/ 3 | *.iml 4 | /classes/ 5 | **/build/ 6 | /out/ 7 | /sit/out/ 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'sit' 2 | project(':sit').name = 'test-service' 3 | rootProject.name='ja-micro' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sixt/ja-micro/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/scripts/start_kafka.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export KAFKA_ADVERTISED_HOST_NAME=$(hostname -i) 4 | exec /usr/bin/start-kafka.sh -------------------------------------------------------------------------------- /sit/src/serviceTest/resources/scripts/start_kafka.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export KAFKA_ADVERTISED_HOST_NAME=$(hostname -i) 4 | exec /usr/bin/start-kafka.sh -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/MessageExecutor.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | 5 | public interface MessageExecutor { 6 | void execute(ConsumerRecord record); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/testrpcclasses/TestService1.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.testrpcclasses; 2 | 3 | import com.google.protobuf.Message; 4 | 5 | public class TestService1 { 6 | 7 | public abstract class TestServiceRequest implements Message { 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/testrpcclasses/TestService2.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.testrpcclasses; 2 | 3 | import com.google.protobuf.Message; 4 | 5 | public class TestService2 { 6 | 7 | public abstract class TestServiceRequest implements Message { 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/testrpcclasses/package1/TestService.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.testrpcclasses.package1; 2 | 3 | import com.google.protobuf.Message; 4 | 5 | public class TestService { 6 | 7 | public abstract class TestServiceRequest implements Message { 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/testrpcclasses/package2/TestService.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.testrpcclasses.package2; 2 | 3 | import com.google.protobuf.Message; 4 | 5 | public class TestService { 6 | 7 | public abstract class TestServiceRequest implements Message { 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | 5 | public interface MessageQueue { 6 | void add(ConsumerRecord record); 7 | void consumed(KafkaTopicInfo topicInfo); 8 | void processingEnded(KafkaTopicInfo topicInfo); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/proto/RpcEnvelope.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package RpcEnvelope; 4 | option java_multiple_files = false; 5 | option java_package = "com.sixt.service.framework.protobuf"; 6 | 7 | message Request { 8 | string service_method = 1; 9 | fixed64 sequence_number = 2; 10 | } 11 | 12 | message Response { 13 | string service_method = 1; 14 | fixed64 sequence_number = 2; 15 | string error = 3; 16 | } -------------------------------------------------------------------------------- /travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Travis-CI builds 4 | TRAVIS_TAG=`git tag --contains` 5 | 6 | if ([ -n "${TRAVIS_TAG}" ]); then 7 | echo "Running release build" 8 | ./gradlew release -Prelease.useAutomaticVersion=true -Prelease.releaseVersion="$TRAVIS_TAG" -x preTagCommit -x createReleaseTag -x checkCommitNeeded -x commitNewVersion 9 | else 10 | echo "Running CI build" 11 | ./gradlew build 12 | fi -------------------------------------------------------------------------------- /sit/src/main/proto/AnotherService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.sixt.service.another_service.api; 4 | 5 | option java_multiple_files = true; 6 | option java_package = "com.sixt.service.another_service.api"; 7 | 8 | service AnotherService { 9 | rpc ImpersonatorTest (ImpersonatorTestCommand) returns (ImpersonatorTestResponse) {} 10 | } 11 | 12 | message ImpersonatorTestCommand { 13 | } 14 | 15 | message ImpersonatorTestResponse { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/injection/MessagingModule.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.injection; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.sixt.service.framework.kafka.messaging.ConsumerFactory; 5 | 6 | public class MessagingModule extends AbstractModule { 7 | 8 | @Override 9 | protected void configure() { 10 | bind(ConsumerFactory.class).toProvider(ConsumerFactoryProvider.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/proto/TestEvent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package schema; 3 | 4 | option java_multiple_files = false; 5 | option java_package = "com.sixt.service.framework.test"; 6 | 7 | message Meta { 8 | string name = 1; 9 | string timestamp = 2; 10 | string correlation_id = 3; 11 | string grouping = 4; 12 | string distribution_key = 5; 13 | } 14 | 15 | // Key: vehicle_id 16 | message OneTestEvent { 17 | Meta meta = 1; 18 | 19 | string vehicle_id = 10; 20 | } -------------------------------------------------------------------------------- /sit/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sit/src/serviceTest/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/backoff/DefaultExponentialBackOff.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc.backoff; 2 | 3 | /** 4 | * Default implementation of exponential wait. This causes a sleep 5 | * for (10 ^ call_counter) seconds between retries 6 | */ 7 | public final class DefaultExponentialBackOff extends ExponentialRetryBackOff { 8 | 9 | private static final float DEFAULT_BASE = 10; 10 | 11 | public DefaultExponentialBackOff() { 12 | super(DEFAULT_BASE); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/proto/MessagingTest.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.sixt.service.framework.kafka.messaging; 4 | 5 | option java_multiple_files = true; 6 | 7 | message EmptyMessage { 8 | 9 | } 10 | 11 | message SayHelloToCmd { 12 | string name = 1; 13 | } 14 | 15 | message SayHelloToReply { 16 | string greeting = 1; 17 | } 18 | 19 | message TypeDictionaryTest{ 20 | int32 cruftlevel = 1; 21 | } 22 | 23 | message TestMessageWithNoHandler { 24 | string a_meaningless_field = 1; 25 | } 26 | 27 | message TreeNode { 28 | string name = 1; 29 | repeated TreeNode children = 2; 30 | } -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/rpc/RpcCallExceptionDecoderTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class RpcCallExceptionDecoderTest { 8 | 9 | @Test 10 | public void verifyExceptionToString() { 11 | Exception ex = new IllegalStateException("testing"); 12 | String result = RpcCallExceptionDecoder.exceptionToString(ex); 13 | assertThat(result).contains("Exception: IllegalStateException Message: testing " + 14 | "Stacktrace: java.lang.IllegalStateException: testing"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | %d [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/logback-base.xml: -------------------------------------------------------------------------------- 1 | 2 | System.out 3 | 4 | false 5 | UTC 6 | 7 | time 8 | thread-id 9 | [ignore] 10 | [ignore] 11 | logger 12 | stacktrace 13 | 14 | correlation-id 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.util; 2 | 3 | import java.io.File; 4 | 5 | public class FileUtil { 6 | 7 | public static String stripPath(String fileName) { 8 | if (fileName == null) { 9 | return ""; 10 | } 11 | return fileName.substring(fileName.lastIndexOf(File.separatorChar)+1, fileName.length()); 12 | } 13 | 14 | public static String stripExtension(String fileName) { 15 | if (fileName == null) { 16 | return ""; 17 | } 18 | if (!fileName.contains(".")) { 19 | return fileName; 20 | } 21 | return fileName.substring(0, fileName.lastIndexOf('.')); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/kafka/QueueTypeEnumTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.mockito.Mockito.mock; 7 | 8 | public class QueueTypeEnumTest { 9 | 10 | @Test 11 | public void getMessageQueueInstance_succes() { 12 | assertThat(KafkaSubscriber.QueueType.OffsetBlocking.getMessageQueueInstance(mock(MessageExecutor.class), 100)) 13 | .isInstanceOf(OffsetBlockingMessageQueue.class); 14 | assertThat(KafkaSubscriber.QueueType.Eager.getMessageQueueInstance(mock(MessageExecutor.class), 100)) 15 | .isInstanceOf(EagerMessageQueue.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | sudo: required 4 | group: edge 5 | jdk: 6 | - oraclejdk8 7 | script: ./travis-build.sh 8 | env: 9 | DOCKER_COMPOSE_VERSION: 1.11.2 10 | before_install: 11 | - docker --version 12 | - docker-compose --version 13 | - which docker-compose 14 | - sudo mv /usr/local/bin/docker-compose /usr/local/bin/docker-compose-old 15 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 16 | - chmod +x docker-compose 17 | - sudo mv docker-compose /usr/local/bin 18 | - docker-compose --version 19 | install: true #Skip the install step; otherwise gradle assemble is called by default. 20 | branches: 21 | except: 22 | - /^[0-9.]+/ 23 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/MetricBuilderFactory.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.metrics; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Provider; 5 | import com.google.inject.Singleton; 6 | 7 | @Singleton 8 | public class MetricBuilderFactory { 9 | private final Provider metricBuilderProvider; 10 | 11 | @Inject 12 | public MetricBuilderFactory(Provider metricBuilderProvider) { 13 | this.metricBuilderProvider = metricBuilderProvider; 14 | } 15 | 16 | public MetricBuilder newMetric(String baseName) { 17 | MetricBuilder metricBuilder = metricBuilderProvider.get(); 18 | metricBuilder.setBaseName(baseName); 19 | return metricBuilder; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/EagerMessageQueue.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | 5 | public class EagerMessageQueue implements MessageQueue { 6 | 7 | private MessageExecutor messageExecutor; 8 | 9 | public EagerMessageQueue(MessageExecutor messageExecutor, long retryDelayMillis) { 10 | this.messageExecutor = messageExecutor; 11 | } 12 | 13 | @Override 14 | public void add(ConsumerRecord record) { 15 | messageExecutor.execute(record); 16 | } 17 | 18 | @Override 19 | public void consumed(KafkaTopicInfo topicInfo) { 20 | } 21 | 22 | @Override 23 | public void processingEnded(KafkaTopicInfo topicInfo) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | public interface IntegrationTest {} 16 | 17 | -------------------------------------------------------------------------------- /src/test/proto/FrameworkTest.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package FrameworkTest; 4 | option java_multiple_files = false; 5 | option java_package = "com.sixt.service.framework.protobuf"; 6 | 7 | message Foobar { 8 | repeated string blah = 1; 9 | } 10 | 11 | message SerializationSubMessage { 12 | string id = 1; 13 | } 14 | 15 | message SerializationTest { 16 | string id = 1; 17 | string id2 = 2; 18 | SerializationSubMessage sub_message = 3; 19 | string id4 = 4; 20 | } 21 | 22 | enum Error { 23 | NO_ERROR = 0; 24 | INVALID_VEHICLE_ID = 1; 25 | } 26 | 27 | message MessageWithEnum { 28 | Error error = 1; 29 | } 30 | 31 | message MessageWithMap { 32 | map error_map = 1; 33 | } 34 | 35 | message CamelCaseTestMessage { 36 | string user_id = 1; 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/metrics/MetricBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.metrics; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import org.junit.Test; 5 | 6 | import java.util.stream.IntStream; 7 | 8 | public final class MetricBuilderTest { 9 | 10 | @Test 11 | public void testShouldVerifyNoDuplicateMetricRegistered() { 12 | final MetricRegistry metricRegistry = new MetricRegistry(); 13 | 14 | final MetricBuilderFactory metricBuilderFactory = new MetricBuilderFactory( 15 | () -> new MetricBuilder(metricRegistry) 16 | ); 17 | 18 | IntStream.rangeClosed(0, 500) 19 | .parallel() 20 | .forEach(i -> metricBuilderFactory.newMetric("metric_name").buildCounter().incSuccess()); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/backoff/ExponentialRetryBackOff.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc.backoff; 2 | 3 | import java.time.Duration; 4 | 5 | /** 6 | * This causes a sleep for (exponentialBase ^ retryCounter) seconds between retries 7 | */ 8 | public class ExponentialRetryBackOff implements RetryBackOffFunction { 9 | 10 | private final float exponentialBase; 11 | 12 | public ExponentialRetryBackOff(final float base) { 13 | if (base <= 1) { 14 | throw new IllegalArgumentException("Base cannot be <= 1"); 15 | } 16 | exponentialBase = base; 17 | } 18 | 19 | @Override 20 | public final Duration timeout(final int retryCounter) { 21 | return Duration.ofSeconds((long) Math.pow(exponentialBase, retryCounter)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /sit/docker_clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set -o errexit 4 | 5 | SERVICE=$1 6 | 7 | echo "Cleaning up docker images of $1..." 8 | 9 | unamestr=`uname` 10 | if [[ "$unamestr" == 'Linux' ]]; then 11 | XARGS_CMD='xargs -r' 12 | else 13 | XARGS_CMD='xargs' 14 | fi 15 | 16 | # Stop running docker containers 17 | image_prefix=$(docker ps | grep ${SERVICE} | awk '!NF || !seen[$3]++' | awk '{print $2}' | awk -F_ '{print $1}') 18 | 19 | if [[ ! -z "${image_prefix// }" ]]; then 20 | docker ps | grep "${image_prefix}_" | awk '{print $1}' | xargs -r docker stop 21 | fi 22 | 23 | # Remove docker images 24 | docker_images=$(docker images | grep "${SERVICE}" | awk '!NF || !seen[$3]++' | awk '{print $3}') 25 | 26 | if [[ ! -z "${docker_images// }" ]]; then 27 | echo $docker_images | ${XARGS_CMD} docker rmi -f 28 | fi 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/SaslConfigurator.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.Properties; 6 | 7 | public class SaslConfigurator { 8 | 9 | public void configureSasl(Properties props, String username, String password) { 10 | if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) { 11 | String jaasTemplate = "org.apache.kafka.common.security.plain.PlainLoginModule " + 12 | "required username=\"%s\" password=\"%s\";"; 13 | props.put("security.protocol", "SASL_PLAINTEXT"); 14 | props.put("sasl.mechanism", "PLAIN"); 15 | props.put("sasl.jaas.config", String.format(jaasTemplate, username, password)); 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/GaugeGetter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | public interface GaugeGetter { 16 | 17 | long getValue(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/tracing/TracingProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.tracing; 14 | 15 | import io.opentracing.Tracer; 16 | 17 | public interface TracingProvider { 18 | Tracer getTracer(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/configuration/ChangeCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.configuration; 14 | 15 | public interface ChangeCallback { 16 | 17 | void entryChanged(String name, String value); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/EventReceivedCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka; 14 | 15 | public interface EventReceivedCallback { 16 | 17 | void eventReceived(TYPE message, KafkaTopicInfo topicInfo); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/rpc/backoff/ExponentialRetryBackOffTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc.backoff; 2 | 3 | import junitparams.JUnitParamsRunner; 4 | import junitparams.Parameters; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import java.time.Duration; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(JUnitParamsRunner.class) 13 | public class ExponentialRetryBackOffTest { 14 | 15 | @Test 16 | @Parameters({"2, 0, 1", "2, 1, 2", "2, 5, 32", "10, 0, 1", "10, 2, 100", "10, 4, 10000"}) 17 | public void verifyTimeout(int base, int retryCount, long expectedSeconds) { 18 | ExponentialRetryBackOff backoff = new ExponentialRetryBackOff(base); 19 | Duration duration = backoff.timeout(retryCount); 20 | assertThat(duration.toMillis()).isEqualTo(expectedSeconds * 1000); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/logging/LoggingContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | public interface LoggingContext { 16 | 17 | void updateLoggerLogLevel(String newLevel); 18 | void updateLoggerLogLevel(String loggerName, String newLevel); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/UnknownMessageTypeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | public class UnknownMessageTypeException extends Exception { 16 | public UnknownMessageTypeException(MessageType type) { 17 | super(type.toString()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/UnknownMessageHandlerException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | 16 | public class UnknownMessageHandlerException extends Exception { 17 | public UnknownMessageHandlerException(MessageType type) { 18 | super(type.toString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/logging/SixtLogger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | import org.slf4j.Marker; 16 | 17 | import static net.logstash.logback.marker.Markers.append; 18 | 19 | public class SixtLogger { 20 | 21 | public static Marker TRAFFIC = append("category", "traffic"); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/RetryDelayer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | 16 | public interface RetryDelayer { 17 | 18 | // returns true if we should still retrying 19 | boolean delay(); 20 | 21 | // reset the previous attempt if we have a new retry-case 22 | void reset(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/MetricTag.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | public class MetricTag { 16 | 17 | protected String name; 18 | protected String value; 19 | 20 | public MetricTag(String name, String value) { 21 | this.name = name; 22 | this.value = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sit/src/main/proto/Messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | 4 | // Asynchronous messaging conventions 5 | 6 | // 1. The default namespace of the asynchronous contract is com.sixt.service.{SERVICENAME}.api 7 | // This namespace is currently shared with the RPC messages to allow dual use (sync/async) of messages. 8 | package com.sixt.service.test_service.api; 9 | 10 | // 2. For each top-level type in this file, we will generate a separate file/class to avoid the outer class wrapper. 11 | // For example, we will get classes like com.sixt.service.test_service.api.Greeting 12 | option java_multiple_files = true; 13 | 14 | // 3. There is no need for the redundant option java_package directive 15 | 16 | // 4. The Java Type.getTypeName() is used as string message_type in the MessagingEnvelope 17 | // See also class MessageType 18 | 19 | 20 | 21 | // These message form the asynchronous messaging contract of the test_service 22 | message Greeting { 23 | string greeting = 1; 24 | } 25 | 26 | message Echo { 27 | string echo = 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/ServiceMethodHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | import com.google.protobuf.Message; 16 | import com.sixt.service.framework.rpc.RpcCallException; 17 | 18 | public interface ServiceMethodHandler { 19 | 20 | RES handleRequest(REQ request, OrangeContext ctx) throws RpcCallException; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/proto/Messaging.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.sixt.service.framework.kafka.messaging; 4 | 5 | option java_multiple_files = true; 6 | 7 | message Envelope { 8 | // Header fields: 9 | string message_id = 1; // REQUIRED. A unique identfier for this message. 10 | 11 | // - Tracing/Correlation 12 | string correlation_id = 10; // OPTIONAL. A correlation id to correlate multiple messages, rpc request/responses, etc. belonging to a trace/flow/user request/etc.. 13 | 14 | // - Message exchange patterns 15 | // -- Request/Reply 16 | string request_correlation_id = 20; // REQUIRED for RESPONSE. This response correlates to the message id of the original request. 17 | string reply_to = 21; // REQUIRED for REQUEST. Send responses for this request to the given address. See class Topic for syntax. 18 | 19 | 20 | // Payload: 21 | // inner message type and serialized bytes 22 | string message_type = 100; // REQUIRED. See class MessageType for syntax. 23 | bytes innerMessage = 101; // REQUIRED. byte[] for the serialized inner protobuf message 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/util/Sleeper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.util; 14 | 15 | public class Sleeper { 16 | 17 | public void sleep(long time) throws InterruptedException { 18 | Thread.sleep(time); 19 | } 20 | 21 | public void sleepNoException(long time) { 22 | try { 23 | Thread.sleep(time); 24 | } catch (InterruptedException e) { 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/servicetest/mockservice/ServiceImpersonatorLoadBalancerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.mockservice; 14 | 15 | import com.sixt.service.framework.util.ProcessUtil; 16 | import org.junit.Test; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | 21 | public class ServiceImpersonatorLoadBalancerTest { 22 | 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/MetricsReporterProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | /** 16 | * To provide metrics reporting in a way specific to your environment, implement 17 | * a class with the MetricsReporterPlugin annotation and implement this interface. 18 | * At Sixt, we use the influxdb format with arbitrarily-formatted tags. 19 | */ 20 | public interface MetricsReporterProvider { 21 | 22 | void initialize(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/util/ProcessUtil.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.util; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.List; 9 | 10 | public class ProcessUtil { 11 | 12 | private static final Logger logger = LoggerFactory.getLogger(ProcessUtil.class); 13 | 14 | public String runProcess(List command) { 15 | String output = null; 16 | String debug = String.join(" ", command); 17 | logger.debug("Running command {}", debug); 18 | ProcessBuilder builder = new ProcessBuilder(command); 19 | try { 20 | builder.redirectErrorStream(true); 21 | Process process = builder.start(); 22 | process.waitFor(); 23 | output = IOUtils.toString(process.getInputStream(), Charset.defaultCharset()); 24 | logger.debug("Command returned: {}", output); 25 | } catch (Exception e) { 26 | logger.warn("Caught exception running command", e); 27 | } 28 | return output; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/util/MockMethodHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.util; 14 | 15 | import com.google.protobuf.Message; 16 | 17 | // marker interface to bypass normal reflection mechanism for 18 | // our contract-testing infrastructure 19 | public interface MockMethodHandler { 20 | 21 | Class getRequestType(); 22 | 23 | Class getResponseType(); 24 | 25 | int getMethodCallCounter(); 26 | 27 | void resetMethodCallCounter(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/HealthCheckProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * Annotation to indicate this class provides information for a health check. 22 | * This class should also implement HealthCheckContributor. 23 | */ 24 | @Target(ElementType.TYPE) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface HealthCheckProvider { 27 | } 28 | -------------------------------------------------------------------------------- /src/test/resources/docker-compose-integrationtest.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | zookeeper: 5 | image: wurstmeister/zookeeper 6 | ports: 7 | - "2181" 8 | hostname: zookeeper 9 | 10 | kafka: 11 | image: wurstmeister/kafka:0.10.1.0-1 12 | ports: 13 | - "9092" 14 | environment: 15 | KAFKA_ADVERTISED_PORT: 9092 16 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 17 | KAFKA_CREATE_TOPICS: "ping:3:1,pong:3:1,events:3:1" # events mjust be last 18 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" 19 | volumes: 20 | - /var/run/docker.sock:/var/run/docker.sock 21 | - ./scripts/start_kafka.sh:/tmp/start_kafka.sh 22 | depends_on: 23 | - zookeeper 24 | entrypoint: /tmp/start_kafka.sh 25 | hostname: kafka 26 | healthcheck: 27 | # nota bene: test command is executed inside the container 28 | # the DockerComposeRule will check if this services goes to state healthy by this healthcheck 29 | test: ["CMD-SHELL", "/opt/kafka_2.11-0.10.1.0/bin/kafka-topics.sh --zookeeper zookeeper:2181 --list | grep 'events$$' "] 30 | interval: 10s # test is executed every 10s 31 | timeout: 10s # timeout per test cmd 32 | retries: 12 # try 12*10s = 120s = 2min -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/OrangeMicroservice.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * This annotation is to indicate the main entry-point for a service. This 22 | * class should implement AbstractService and conform to the Service Frmework. 23 | */ 24 | @Target(ElementType.TYPE) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface OrangeMicroservice { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/registry/ServiceRegistrationProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.registry; 14 | 15 | import com.sixt.service.framework.MethodHandlerDictionary; 16 | 17 | public interface ServiceRegistrationProvider { 18 | 19 | /** 20 | * Called during service startup with a populated MethodHandlerDictionary. 21 | * This dictionary cannot be dynamically altered during runtime. Typically, 22 | * the implementer should report the initial service state information here. 23 | */ 24 | void initialize(MethodHandlerDictionary methodHandlers); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/health/HealthCheckContributor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.health; 14 | 15 | /** 16 | * Interface for a contributor to the service's health status. 17 | * This is the polling flavor. It will be called every few seconds. 18 | * There is also an on-demand flavor. TBD 19 | */ 20 | public interface HealthCheckContributor { 21 | 22 | HealthCheck getHealthCheck(); 23 | 24 | /** 25 | * Give a check the chance to initialize and then make a choice about whether 26 | * it will participate. 27 | */ 28 | boolean shouldRegister(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/injection/OrangeServletModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.injection; 14 | 15 | import com.google.inject.servlet.ServletModule; 16 | import com.sixt.service.framework.jetty.HealthServlet; 17 | import com.sixt.service.framework.jetty.RpcServlet; 18 | 19 | public class OrangeServletModule extends ServletModule { 20 | 21 | @Override 22 | protected void configureServlets() { 23 | bind(HealthServlet.class); 24 | bind(RpcServlet.class); 25 | 26 | serve("/health").with(HealthServlet.class); 27 | serve("/").with(RpcServlet.class); 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/tracing/NoopTracingProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.tracing; 14 | 15 | import com.sixt.service.framework.annotation.TracingPlugin; 16 | import io.opentracing.Tracer; 17 | import io.opentracing.noop.NoopTracerFactory; 18 | 19 | /** 20 | * This is the default tracing plugin which just uses the OpenTracing Noop implementation. 21 | * That way, tracing can be effectively disabled. 22 | */ 23 | @TracingPlugin(name = "noop") 24 | public class NoopTracingProvider implements TracingProvider { 25 | 26 | @Override 27 | public Tracer getTracer() { 28 | return NoopTracerFactory.create(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/ServiceMethodPreHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | import com.google.protobuf.Message; 16 | import com.sixt.service.framework.rpc.RpcCallException; 17 | 18 | public interface ServiceMethodPreHook { 19 | 20 | /** 21 | * Perform an action and/or modify an incoming request 22 | * @param request The request being made 23 | * @param ctx The OrangeContext for the request 24 | * @return Either the incoming request, or a new request 25 | * @throws RpcCallException 26 | */ 27 | REQ handleRequest(REQ request, OrangeContext ctx) throws RpcCallException; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Why Should This Be In Core? 19 | 20 | 21 | 22 | ### Benefits 23 | 24 | 25 | 26 | ### Possible Drawbacks 27 | 28 | 29 | 30 | ### Applicable Issues 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/MetricsReporterPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * This is a marker annotation to denote this is the Plugin for the metrics 22 | * reporter with the given name. The class using this annotation must also 23 | * implement MetricsReporterProvider. 24 | */ 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface MetricsReporterPlugin { 28 | String name(); 29 | } 30 | -------------------------------------------------------------------------------- /sit/src/main/proto/TestService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.sixt.service.test_service.api; 4 | 5 | option java_multiple_files = false; 6 | option java_package = "com.sixt.service.test_service.api"; 7 | 8 | service TestService { 9 | rpc GetRandomString (GetRandomStringQuery) returns (RandomStringResponse) {} 10 | rpc SlowResponder (GetRandomStringQuery) returns (RandomStringResponse) {} 11 | rpc SetHealthCheckStatus (SetHealthCheckStatusCommand) returns (SetHealthCheckStatusResponse) {} 12 | rpc CallsAnotherService (CallAnotherServiceCommand) returns (CallAnotherServiceResponse) {} 13 | } 14 | 15 | message GetRandomStringQuery { 16 | string input = 1; 17 | } 18 | 19 | message RandomStringResponse { 20 | string random = 1; 21 | } 22 | 23 | message SetHealthCheckStatusCommand { 24 | string status = 1; 25 | string message = 2; 26 | } 27 | 28 | message SetHealthCheckStatusResponse { 29 | } 30 | 31 | message RandomSampleEvent { 32 | Meta meta = 1; 33 | string id = 2; 34 | string message = 3; 35 | } 36 | 37 | message HandlerSuccessEvent { 38 | Meta meta = 1; 39 | string id = 2; 40 | string message = 3; 41 | } 42 | 43 | message Meta { 44 | string name = 1; 45 | } 46 | 47 | message CallAnotherServiceCommand { 48 | } 49 | 50 | message CallAnotherServiceResponse { 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/TracingPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * This is a marker annotation to denote this is the Plugin for the tracing 22 | * implementation with the given name. The class using this annotation must also 23 | * implement com.sixt.service.framework.tracing.TracingProvider. 24 | */ 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface TracingPlugin { 28 | String name(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/RpcHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.RetentionPolicy; 17 | 18 | /** 19 | * This annotation is used to annotate that a class is an RpcHandler. The framework 20 | * uses classpath scanning to find the RpcHandlers and registers them automatically. 21 | * Classes with this annotation should also implement ServiceMethodHandler 22 | */ 23 | @Retention(RetentionPolicy.RUNTIME) 24 | public @interface RpcHandler { 25 | 26 | /** 27 | * The name under which the handler will be registered 28 | */ 29 | String value(); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/ServiceMethodPostHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | import com.google.protobuf.Message; 16 | import com.sixt.service.framework.rpc.RpcCallException; 17 | 18 | public interface ServiceMethodPostHook { 19 | 20 | /** 21 | * Perform an action and/or modify an outgoing response 22 | * @param response The response being returned from the ServiceMethodHandler 23 | * @param ctx The OrangeContext for the request 24 | * @return Either the outgoing response, or a new response 25 | * @throws RpcCallException 26 | */ 27 | RES handleRequest(RES response, OrangeContext ctx) throws RpcCallException; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/ServiceRegistryPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * This is a marker annotation to denote this is the Plugin for the service 22 | * registry with the given name. The class using this annotation must also 23 | * implement ServiceDiscoveryProvider and/or ServiceRegistrationProvider. 24 | */ 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface ServiceRegistryPlugin { 28 | String name(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/ConfigurationPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * This is a marker annotation to denote this is the Plugin for the service 22 | * configuration with the given name. The class using this annotation must also 23 | * implement com.sixt.service.framework.configuration.ConfigurationProvider. 24 | */ 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface ConfigurationPlugin { 28 | String name(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/configuration/ConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.configuration; 14 | 15 | /** 16 | * To provide configuration data in a way specific to your environment, implement 17 | * a class with the ConfigurationPlugin annotation and implement this interface. 18 | * Early in service startup, initialize() will be called, and you will have the 19 | * opportunity to do whatever you wish. At Sixt, we have a dedicated microservice 20 | * to serve the configuration data, and long-polling is implemented to get 21 | * immediate updates when a configuration value is modified / added. 22 | */ 23 | public interface ConfigurationProvider { 24 | 25 | void initialize(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/annotation/EnableDatabaseMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.annotation; 14 | 15 | import java.lang.annotation.ElementType; 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | import java.lang.annotation.Target; 19 | 20 | /** 21 | * Annotation to indicate a database migration should be performed at startup. 22 | * SystemProperties must contain databaseServer, databaseUsername and databasePassword 23 | * Status of connecting to the database and migration operation will be reported 24 | * through a healthcheck. 25 | */ 26 | @Target(ElementType.TYPE) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | public @interface EnableDatabaseMigration { 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/registry/ServiceDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.registry; 14 | 15 | import com.sixt.service.framework.rpc.LoadBalancer; 16 | 17 | public interface ServiceDiscoveryProvider { 18 | 19 | /** 20 | * Called when a LoadBalancer is created for a service dependency. 21 | * Typically the implementer should start up a thread to monitor the 22 | * service, and when state about the instances change, the LoadBalancer 23 | * is notified of the changes by the updateServiceEndpoints call. 24 | */ 25 | void monitorService(LoadBalancer lb); 26 | 27 | /** 28 | * Allow the registration monitor to shutdown cleanly 29 | */ 30 | void shutdown(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/registry/consul/ConsulHealthEntryFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.registry.consul; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class ConsulHealthEntryFilter { 19 | 20 | public static List filterHealthyInstances(List healths) { 21 | List retval = new ArrayList<>(); 22 | if (healths != null) { 23 | for (ConsulHealthEntry entry : healths) { 24 | if (ConsulHealthEntry.Status.Passing.equals(entry.getStatus())) { 25 | retval.add(entry); 26 | } 27 | } 28 | } 29 | return retval; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/injection/InjectionModuleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.injection; 14 | 15 | import com.sixt.service.framework.MethodHandlerDictionary; 16 | import com.sixt.service.framework.ServiceProperties; 17 | import org.junit.Test; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | public class InjectionModuleTest { 22 | 23 | @Test 24 | public void testSetterAndGetter() { 25 | InjectionModule module = new InjectionModule(new ServiceProperties()); 26 | MethodHandlerDictionary dict = new MethodHandlerDictionary(); 27 | module.setMethodHandlerDictionary(dict); 28 | assertThat(module.getMethodHandlers()).isEqualTo(dict); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/MessageHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import com.sixt.service.framework.OrangeContext; 16 | 17 | 18 | public interface MessageHandler { 19 | 20 | /** 21 | * Callback interface to hand over a message. 22 | *

23 | * Implementors need to consider that we have at least once deliery, i.e. messages may be delivered multiple times (and potentially out of order). 24 | * Thus, message handlers need to handle duplicate messages gracefully / be idempotent. 25 | * 26 | * @param message 27 | * @param context 28 | */ 29 | void onMessage(Message message, OrangeContext context); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/Message.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | public final class Message { 16 | private final T payload; 17 | private final Metadata metadata; 18 | 19 | public Message(T payload, Metadata metadata) { 20 | this.payload = payload; 21 | this.metadata = metadata; 22 | } 23 | 24 | public T getPayload() { 25 | return payload; 26 | } 27 | 28 | public Metadata getMetadata() { 29 | return metadata; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Message{" + 35 | "payload=" + payload + 36 | ", metadata=" + metadata + 37 | '}'; 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/FailedMessageProcessor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | 16 | /** 17 | * A Strategy interface to allow exchangeable failure handling behaviour. 18 | */ 19 | public interface FailedMessageProcessor { 20 | 21 | /** 22 | * This method decides if a failed message should be re-tried or not. 23 | *

24 | * It may block the current thread (which is calling the message handler) if a delay between retries is required. 25 | * 26 | * @param failed the failed message 27 | * @param failureCause the root cause of the failure 28 | * @return true if message delivery should be re-tried, false otherwise 29 | */ 30 | boolean onFailedMessage(Message failed, Throwable failureCause); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/configuration/LogLevelChangeCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.configuration; 14 | 15 | import com.sixt.service.framework.logging.LoggingContext; 16 | 17 | import javax.validation.constraints.NotNull; 18 | 19 | /** 20 | * This class should update the current log level when the callback is fired on configuration change. 21 | */ 22 | public class LogLevelChangeCallback implements ChangeCallback { 23 | 24 | private LoggingContext loggingContext; 25 | 26 | @NotNull 27 | public LogLevelChangeCallback(LoggingContext loggingContext) { 28 | this.loggingContext = loggingContext; 29 | } 30 | 31 | @Override 32 | public void entryChanged(String name, String value) { 33 | this.loggingContext.updateLoggerLogLevel("" + value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/servicetest/helper/DockerPortResolverTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.helper; 14 | 15 | import com.sixt.service.framework.util.ProcessUtil; 16 | import org.junit.Test; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | 21 | public class DockerPortResolverTest { 22 | 23 | private DockerPortResolver portResolver = new DockerPortResolver(mock(ProcessUtil.class)); 24 | 25 | @Test 26 | public void parseExposedPortTest() { 27 | String json = "[{\"NetworkSettings\": {\"Ports\": {\"5005/tcp\": " + 28 | "[{\"HostIp\": \"0.0.0.0\",\"HostPort\": \"33240\"}]}}}]"; 29 | int result = portResolver.parseExposedPort(json, 5005); 30 | assertThat(result).isEqualTo(33240); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/GoGauge.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | import com.codahale.metrics.Gauge; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class GoGauge implements Gauge { 21 | 22 | protected Map elements = new HashMap<>(); 23 | protected String name; 24 | 25 | public GoGauge(String name) { 26 | this.name = name; 27 | } 28 | 29 | public void register(String name, GaugeGetter getter) { 30 | elements.put(name, getter); 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public Map getElements() { 38 | return elements; 39 | } 40 | 41 | @Override 42 | public Long getValue() { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/DiscardFailedMessages.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | /** 19 | * Discard any messages that caused the MessageHandler to throw an exception. 20 | *

21 | * It logs topic and offset of the message, so a out-of-bounds mechanism can process / re-try any failed messages. 22 | */ 23 | public class DiscardFailedMessages implements FailedMessageProcessor { 24 | private static final Logger logger = LoggerFactory.getLogger(DiscardFailedMessages.class); 25 | 26 | @Override 27 | public boolean onFailedMessage(Message failed, Throwable failureCause) { 28 | logger.warn(failed.getMetadata().getLoggingMarker(), "Discarded failing message.", failureCause); 29 | 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/kafka/KafkaSubscriberJsonFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka; 14 | 15 | import com.sixt.service.framework.ServiceProperties; 16 | import org.junit.Test; 17 | 18 | // Simply illustrates how to use the factory 19 | public class KafkaSubscriberJsonFactoryTest implements EventReceivedCallback { 20 | 21 | private KafkaSubscriber subscriber; 22 | 23 | @Test 24 | public void buildSubscriber() throws Exception { 25 | ServiceProperties properties = new ServiceProperties(); 26 | KafkaSubscriberFactory factory = new KafkaSubscriberFactory<>(properties, null, null); 27 | subscriber = factory.newBuilder("test", this).build(); 28 | } 29 | 30 | @Override 31 | public void eventReceived(String message, KafkaTopicInfo topicInfo) { 32 | subscriber.consume(topicInfo); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/servicetest/eventing/EventUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.eventing; 14 | 15 | import com.google.gson.JsonElement; 16 | import com.google.gson.JsonObject; 17 | 18 | public final class EventUtils { 19 | 20 | protected static final String META = "meta"; 21 | protected static final String EVENT_TYPE = "eventType"; 22 | protected static final String NAME = "name"; 23 | 24 | public static String getEventName(JsonObject event) { 25 | String eventName = null; 26 | if (event.has(META)) { 27 | eventName = event.getAsJsonObject(META).get(NAME).getAsString(); 28 | } else { 29 | JsonElement eventType = event.get(EVENT_TYPE); 30 | if (eventType != null) { 31 | eventName = eventType.getAsString(); 32 | } 33 | } 34 | return eventName; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/injection/OrangeServletModuleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.injection; 14 | 15 | import com.google.inject.Guice; 16 | import com.google.inject.Injector; 17 | import com.sixt.service.framework.ServiceProperties; 18 | import com.sixt.service.framework.jetty.HealthServlet; 19 | import com.sixt.service.framework.jetty.RpcServlet; 20 | import org.junit.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | public class OrangeServletModuleTest { 25 | 26 | @Test 27 | public void testConfigure() { 28 | OrangeServletModule module = new OrangeServletModule(); 29 | Injector injector = Guice.createInjector(module, new TracingModule(new ServiceProperties())); 30 | assertThat(injector.getInstance(HealthServlet.class)).isNotNull(); 31 | assertThat(injector.getInstance(RpcServlet.class)).isNotNull(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/proto/configuration.proto: -------------------------------------------------------------------------------- 1 | // I had to add the configuration service definition here, otherwise it would 2 | // have introduced a circular dependency. 3 | 4 | syntax = "proto3"; 5 | 6 | package com.sixt.service.configuration.api; 7 | 8 | option java_multiple_files = false; 9 | option java_package = "com.sixt.service.configuration.api"; 10 | 11 | service Configuration { 12 | rpc GetValues(Filter) returns (ValuesResponse) {} 13 | rpc GetValue(FullPath) returns (ValuesResponse) {} 14 | rpc UpsertValue(FullPath) returns (ValuesResponse) {} 15 | rpc DeleteValue(FullPath) returns (ValuesResponse) {} 16 | rpc ImportAll(Import) returns (ValuesResponse) {} 17 | } 18 | 19 | message Filter { 20 | uint32 changeIndex = 1; 21 | string service = 2; 22 | string version = 3; 23 | string instance = 4; 24 | } 25 | 26 | message VariantDetail { 27 | string minVersion = 1; 28 | string maxVersion = 2; 29 | repeated string instances = 3; 30 | string value = 4; 31 | bool isEncrypted = 5; 32 | } 33 | 34 | message FullPath { 35 | string service = 1; 36 | string name = 2; 37 | VariantDetail detail = 3; 38 | } 39 | 40 | message ImportItem { 41 | string name = 1; 42 | VariantDetail value = 2; 43 | } 44 | 45 | message Import { 46 | repeated ImportItem values = 1; 47 | } 48 | 49 | message ValueResponse { 50 | string name = 1; 51 | string baseValue = 2; 52 | repeated VariantDetail details = 3; 53 | } 54 | 55 | message ValuesResponse { 56 | uint32 changeIndex = 1; 57 | repeated ValueResponse values = 2; 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/kafka/KafkaPublisherTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.kafka.clients.producer.Callback; 4 | import org.apache.kafka.clients.producer.KafkaProducer; 5 | import org.apache.kafka.clients.producer.RecordMetadata; 6 | import org.apache.kafka.common.errors.TimeoutException; 7 | import org.junit.Test; 8 | import org.mockito.ArgumentCaptor; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.mockito.ArgumentMatchers.any; 14 | import static org.mockito.Mockito.when; 15 | import static org.powermock.api.mockito.PowerMockito.mock; 16 | 17 | public class KafkaPublisherTest { 18 | 19 | private KafkaPublisher publisher = new KafkaPublisher("events", null); 20 | 21 | @Test 22 | public void sendFailsReturnsFalse() { 23 | KafkaProducer producer = mock(KafkaProducer.class); 24 | publisher.realProducer = producer; 25 | RecordMetadata metadata = new RecordMetadata(null, 0, 0, 26 | 0, Long.valueOf(0), 0, 0); 27 | ArgumentCaptor captor = ArgumentCaptor.forClass(Callback.class); 28 | when(producer.send(any(), captor.capture())).then( 29 | invocation -> { 30 | captor.getValue().onCompletion(metadata, new TimeoutException("error")); 31 | return new CompletableFuture(); 32 | }); 33 | String[] events = { "test" }; 34 | assertThat(publisher.publishEvents(false, null, events)).isFalse(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/kafka/EagerMessageQueueTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.mockito.ArgumentCaptor; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.times; 11 | import static org.mockito.Mockito.verify; 12 | 13 | public class EagerMessageQueueTest { 14 | 15 | private MessageExecutor messageExecutor; 16 | private MessageQueue messageQueue; 17 | private String topic = "topic"; 18 | private String defaultKey = "key"; 19 | private String defaultValue = "value"; 20 | 21 | @Before 22 | public void setup() { 23 | messageExecutor = mock(MessageExecutor.class); 24 | messageQueue = new EagerMessageQueue(messageExecutor, 5000); 25 | } 26 | 27 | @Test 28 | public void queue_addTwoRecord_allExecuted() { 29 | ConsumerRecord record1 = new ConsumerRecord<>(topic, 0, 0, defaultKey, defaultValue); 30 | messageQueue.add(record1); 31 | ConsumerRecord record2 = new ConsumerRecord<>(topic, 0, 0, defaultKey, defaultValue); 32 | messageQueue.add(record2); 33 | 34 | ArgumentCaptor captor = ArgumentCaptor.forClass(ConsumerRecord.class); 35 | verify(messageExecutor, times(2)).execute(captor.capture()); 36 | assertThat(captor.getAllValues().get(0)).isEqualTo(record1); 37 | assertThat(captor.getAllValues().get(1)).isEqualTo(record2); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/handler/RandomHandlerPreHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service.handler; 14 | 15 | import com.sixt.service.framework.OrangeContext; 16 | import com.sixt.service.framework.ServiceMethodPreHook; 17 | import com.sixt.service.framework.rpc.RpcCallException; 18 | import com.sixt.service.test_service.api.TestServiceOuterClass.GetRandomStringQuery; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | public class RandomHandlerPreHook implements ServiceMethodPreHook { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(RandomHandlerPreHook.class); 25 | 26 | @Override 27 | public GetRandomStringQuery handleRequest(GetRandomStringQuery request, OrangeContext ctx) throws RpcCallException { 28 | logger.info("Pre-hook for GetRandomStringQuery"); 29 | return GetRandomStringQuery.newBuilder().setInput("PRE:" + request.getInput()).build(); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/kafka/KafkaSubscriberProtobufFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka; 14 | 15 | import com.sixt.service.framework.ServiceProperties; 16 | import com.sixt.service.framework.protobuf.FrameworkTest; 17 | import org.junit.Test; 18 | 19 | // Simply illustrates how to use the factory 20 | public class KafkaSubscriberProtobufFactoryTest implements EventReceivedCallback { 21 | 22 | private KafkaSubscriber subscriber; 23 | 24 | @Test 25 | public void buildSubscriber() throws Exception { 26 | ServiceProperties properties = new ServiceProperties(); 27 | KafkaSubscriberFactory factory = new KafkaSubscriberFactory(properties, null, null); 28 | subscriber = factory.newBuilder("test", this).build(); 29 | } 30 | 31 | @Override 32 | public void eventReceived(FrameworkTest.Foobar message, KafkaTopicInfo topicInfo) { 33 | subscriber.consume(topicInfo); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/handler/RandomHandlerPostHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service.handler; 14 | 15 | import com.sixt.service.framework.OrangeContext; 16 | import com.sixt.service.framework.ServiceMethodPostHook; 17 | import com.sixt.service.framework.rpc.RpcCallException; 18 | import com.sixt.service.test_service.api.TestServiceOuterClass.RandomStringResponse; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | public class RandomHandlerPostHook implements ServiceMethodPostHook { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(RandomHandlerPostHook.class); 25 | 26 | @Override 27 | public RandomStringResponse handleRequest(RandomStringResponse response, OrangeContext ctx) throws RpcCallException { 28 | logger.info("Post-hook for GetRandomStringQuery"); 29 | return RandomStringResponse.newBuilder().setRandom(response.getRandom() + ":POST").build(); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/handler/SlowRespondingHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service.handler; 14 | 15 | import com.google.inject.Singleton; 16 | import com.sixt.service.framework.OrangeContext; 17 | import com.sixt.service.framework.ServiceMethodHandler; 18 | import com.sixt.service.framework.rpc.RpcCallException; 19 | import com.sixt.service.framework.util.Sleeper; 20 | import com.sixt.service.test_service.api.TestServiceOuterClass.GetRandomStringQuery; 21 | import com.sixt.service.test_service.api.TestServiceOuterClass.RandomStringResponse; 22 | 23 | @Singleton 24 | public class SlowRespondingHandler implements ServiceMethodHandler { 25 | 26 | @Override 27 | public RandomStringResponse handleRequest(GetRandomStringQuery request, OrangeContext ctx) throws RpcCallException { 28 | new Sleeper().sleepNoException(5000); 29 | return RandomStringResponse.getDefaultInstance(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/backoff/RetryBackOffFunction.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc.backoff; 2 | 3 | import java.time.Duration; 4 | 5 | /** 6 | * Interface to be used with {@link com.sixt.service.framework.rpc.RpcClientBuilder}.withRetryBackOff() 7 | * to give the ability to do make the time duration between retries exponential. 8 | */ 9 | @FunctionalInterface 10 | public interface RetryBackOffFunction { 11 | 12 | /** 13 | * Calculate the wait time between retries. After the first attempt has been make, the 14 | * value of retryCounter here will be 0, then monotonically increasing. 15 | */ 16 | Duration timeout(int retryCounter); 17 | 18 | default void execute(int retryCounter) { 19 | Duration timeout = timeout(retryCounter); 20 | if (timeout == null || timeout.isNegative()) { 21 | throw new IllegalArgumentException("Retry timeout cannot be null or negative."); 22 | } 23 | 24 | long startTime = System.currentTimeMillis(); 25 | long endTime = startTime + timeout.toMillis(); 26 | 27 | // we are in a while loop here to protect against spurious interrupts 28 | while (!Thread.currentThread().isInterrupted()) { 29 | long now = System.currentTimeMillis(); 30 | if (now >= endTime) { 31 | break; 32 | } 33 | try { 34 | Thread.sleep(endTime - now); 35 | } catch (InterruptedException e) { 36 | Thread.currentThread().interrupt(); 37 | // we should probably quit if we are interrupted? 38 | return; 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/messaging/MessagingAdaptor.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.test_service.messaging; 2 | 3 | 4 | import com.google.inject.Inject; 5 | import com.sixt.service.framework.OrangeContext; 6 | import com.sixt.service.framework.kafka.messaging.*; 7 | import com.sixt.service.test_service.api.Echo; 8 | import com.sixt.service.test_service.api.Greeting; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class MessagingAdaptor { 13 | private static final Logger logger = LoggerFactory.getLogger(MessagingAdaptor.class); 14 | 15 | // Nota bene: Guice can only inject into static inner classes. 16 | public static class GreetingHandler implements MessageHandler { 17 | private final Producer producer; 18 | 19 | @Inject 20 | public GreetingHandler(ProducerFactory producerFactory) { 21 | producer = producerFactory.createProducer(); 22 | } 23 | 24 | @Override 25 | public void onMessage(Message message, OrangeContext context) { 26 | logger.info("Received {}", message); 27 | 28 | String greeting = message.getPayload().getGreeting(); 29 | 30 | Echo reply = Echo.newBuilder().setEcho(greeting).build(); 31 | 32 | Message replyMessage = Messages.replyTo(message, reply, context); 33 | producer.send(replyMessage); 34 | } 35 | } 36 | 37 | public static class EchoHandler implements MessageHandler { 38 | @Override 39 | public void onMessage(Message message, OrangeContext context) { 40 | logger.info("Received {}", message); 41 | } 42 | } 43 | 44 | 45 | } -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/util/FileUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class FileUtilTest { 8 | 9 | @Test 10 | public void stripPath() { 11 | 12 | assertThat(FileUtil.stripPath("/usr/local/blah/blah/myfileName.txt")).isEqualTo("myfileName.txt"); 13 | assertThat(FileUtil.stripPath("C:/Program\\ Files/Applications/SomeFile.app")).isEqualTo("SomeFile.app"); 14 | assertThat(FileUtil.stripPath("SomeFile.app")).isEqualTo("SomeFile.app"); 15 | assertThat(FileUtil.stripPath("/home/jbloggs/SomeFile.longfileextension")).isEqualTo("SomeFile.longfileextension"); 16 | assertThat(FileUtil.stripPath("")).isEqualTo(""); 17 | assertThat(FileUtil.stripPath(" ")).isEqualTo(" "); 18 | assertThat(FileUtil.stripPath(null)).isEqualTo(""); 19 | 20 | } 21 | 22 | @Test 23 | public void stripExtension() { 24 | 25 | assertThat(FileUtil.stripExtension("/usr/local/blah/blah/myfileName.txt")).isEqualTo("/usr/local/blah/blah/myfileName"); 26 | assertThat(FileUtil.stripExtension("C:/Program\\ Files/Applications/SomeFile.app")).isEqualTo("C:/Program\\ Files/Applications/SomeFile"); 27 | assertThat(FileUtil.stripExtension("SomeFile.app")).isEqualTo("SomeFile"); 28 | assertThat(FileUtil.stripExtension("/home/jbloggs/SomeFile.longfileextension")).isEqualTo("/home/jbloggs/SomeFile"); 29 | assertThat(FileUtil.stripExtension("")).isEqualTo(""); 30 | assertThat(FileUtil.stripExtension(" ")).isEqualTo(" "); 31 | assertThat(FileUtil.stripExtension(null)).isEqualTo(""); 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/servicetest/injection/ServiceUnderTestModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.injection; 14 | 15 | import com.sixt.service.framework.ServiceProperties; 16 | import com.sixt.service.framework.rpc.LoadBalancer; 17 | import com.sixt.service.framework.servicetest.service.ServiceUnderTestLoadBalancer; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | public class ServiceUnderTestModule extends TestInjectionModule { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(ServiceUnderTestModule.class); 24 | 25 | public ServiceUnderTestModule(String serviceName) { 26 | this(serviceName, new ServiceProperties()); 27 | } 28 | 29 | public ServiceUnderTestModule(String serviceName, ServiceProperties props) { 30 | super(serviceName, props); 31 | } 32 | 33 | @Override 34 | protected void configure() { 35 | bind(ServiceProperties.class).toInstance(serviceProperties); 36 | bind(LoadBalancer.class).to(ServiceUnderTestLoadBalancer.class); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/servicetest/mockservice/ImpersonatedPortDictionaryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.mockservice; 14 | 15 | import org.junit.Test; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.assertj.core.api.Assertions.catchThrowable; 19 | 20 | public class ImpersonatedPortDictionaryTest { 21 | 22 | private ImpersonatedPortDictionary dictionary = ImpersonatedPortDictionary.getInstance(); 23 | 24 | @Test 25 | public void testDictionary() { 26 | int port = dictionary.newInternalPortForImpersonated("foo"); 27 | assertThat(port).isEqualTo(42000); 28 | port = dictionary.getInternalPortForImpersonated("foo"); 29 | assertThat(port).isEqualTo(42000); 30 | port = dictionary.newInternalPortForImpersonated("bar"); 31 | assertThat(port).isEqualTo(42001); 32 | Throwable thrown = catchThrowable(() -> { 33 | dictionary.getInternalPortForImpersonated("baz"); 34 | }); 35 | assertThat(thrown).isInstanceOf(IllegalStateException.class); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/resources/docker-compose-kafkafailover-integrationtest.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | zookeeper: 5 | image: wurstmeister/zookeeper 6 | ports: 7 | - "2181" 8 | hostname: zookeeper 9 | 10 | kafka1: 11 | image: wurstmeister/kafka:0.10.1.0-1 12 | ports: 13 | - "9092" 14 | environment: 15 | KAFKA_ADVERTISED_PORT: 9092 16 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 17 | KAFKA_CREATE_TOPICS: "ping:3:2,pong:3:2,events:3:2" 18 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" 19 | volumes: 20 | - /var/run/docker.sock:/var/run/docker.sock 21 | - ./scripts/start_kafka.sh:/tmp/start_kafka.sh 22 | depends_on: 23 | - zookeeper 24 | entrypoint: /tmp/start_kafka.sh 25 | hostname: kafka 26 | 27 | kafka2: 28 | image: wurstmeister/kafka:0.10.1.0-1 29 | ports: 30 | - "9092" 31 | environment: 32 | KAFKA_ADVERTISED_PORT: 9092 33 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 34 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" 35 | volumes: 36 | - /var/run/docker.sock:/var/run/docker.sock 37 | - ./scripts/start_kafka.sh:/tmp/start_kafka.sh 38 | depends_on: 39 | - zookeeper 40 | entrypoint: /tmp/start_kafka.sh 41 | hostname: kafka 42 | 43 | kafka3: 44 | image: wurstmeister/kafka:0.10.1.0-1 45 | ports: 46 | - "9092" 47 | environment: 48 | KAFKA_ADVERTISED_PORT: 9092 49 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 50 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" 51 | volumes: 52 | - /var/run/docker.sock:/var/run/docker.sock 53 | - ./scripts/start_kafka.sh:/tmp/start_kafka.sh 54 | depends_on: 55 | - zookeeper 56 | entrypoint: /tmp/start_kafka.sh 57 | hostname: kafka 58 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/ProducerFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import com.google.inject.Inject; 16 | import com.sixt.service.framework.ServiceProperties; 17 | import org.apache.kafka.clients.CommonClientConfigs; 18 | import org.apache.kafka.clients.producer.ProducerConfig; 19 | 20 | import java.util.Properties; 21 | 22 | public class ProducerFactory { 23 | 24 | private final ServiceProperties serviceProperties; 25 | 26 | @Inject 27 | public ProducerFactory(ServiceProperties serviceProperties) { 28 | this.serviceProperties = serviceProperties; 29 | } 30 | 31 | public Producer createProducer() { 32 | String kafkaBootstrapServers = serviceProperties.getKafkaServer(); 33 | 34 | Properties kafkaProducerConfig = new Properties(); 35 | kafkaProducerConfig.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, kafkaBootstrapServers); 36 | kafkaProducerConfig.put(ProducerConfig.ACKS_CONFIG, "all"); // ensure that records have been replicated to other kafka nodes 37 | 38 | return new Producer(kafkaProducerConfig); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/KafkaPublisherBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka; 14 | 15 | import com.sixt.service.framework.metrics.MetricBuilderFactory; 16 | 17 | import java.util.Map; 18 | 19 | public class KafkaPublisherBuilder { 20 | 21 | private final Map properties; 22 | private final KafkaPublisherFactory parentFactory; 23 | protected final String topic; 24 | protected MetricBuilderFactory metricBuilderFactory; 25 | 26 | KafkaPublisherBuilder(KafkaPublisherFactory factory, String topic, Map properties) { 27 | this.parentFactory = factory; 28 | this.topic = topic; 29 | this.properties = properties; 30 | } 31 | 32 | public KafkaPublisher build() { 33 | KafkaPublisher retval = new KafkaPublisher(topic, properties); 34 | retval.setMetricBuilderFactory(metricBuilderFactory); 35 | parentFactory.builtPublisher(retval); 36 | return retval; 37 | } 38 | 39 | public void setMetricBuilderFactory(MetricBuilderFactory metricBuilderFactory) { 40 | this.metricBuilderFactory = metricBuilderFactory; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/PartitionProcessorFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import com.sixt.service.framework.metrics.MetricBuilderFactory; 16 | import io.opentracing.Tracer; 17 | import org.apache.kafka.common.TopicPartition; 18 | 19 | final class PartitionProcessorFactory { 20 | private final TypeDictionary typeDictionary; 21 | private final FailedMessageProcessor failedMessageProcessor; 22 | private final Tracer tracer; 23 | private final MetricBuilderFactory metricBuilderFactory; 24 | 25 | PartitionProcessorFactory(TypeDictionary typeDictionary, FailedMessageProcessor failedMessageProcessor, Tracer tracer, MetricBuilderFactory metricsBuilderFactory) { 26 | this.typeDictionary = typeDictionary; 27 | this.failedMessageProcessor = failedMessageProcessor; 28 | this.tracer = tracer; 29 | this.metricBuilderFactory = metricsBuilderFactory; 30 | } 31 | 32 | PartitionProcessor newProcessorFor(TopicPartition partitionKey) { 33 | return new PartitionProcessor(partitionKey, typeDictionary, failedMessageProcessor, tracer, metricBuilderFactory); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/database/DatabaseMigrationContributor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.database; 14 | 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | import com.sixt.service.framework.ServiceProperties; 18 | import com.sixt.service.framework.annotation.HealthCheckProvider; 19 | import com.sixt.service.framework.health.HealthCheck; 20 | import com.sixt.service.framework.health.HealthCheckContributor; 21 | 22 | @HealthCheckProvider 23 | @Singleton 24 | public class DatabaseMigrationContributor implements HealthCheckContributor { 25 | 26 | private ServiceProperties serviceProps; 27 | private HealthCheck status = new HealthCheck("database_migration", HealthCheck.Status.PASS, ""); 28 | 29 | @Inject 30 | public DatabaseMigrationContributor(ServiceProperties props) { 31 | this.serviceProps = props; 32 | } 33 | 34 | @Override 35 | public HealthCheck getHealthCheck() { 36 | return status; 37 | } 38 | 39 | @Override 40 | public boolean shouldRegister() { 41 | return true; 42 | } 43 | 44 | public void updateStatus(HealthCheck hc) { 45 | this.status = hc; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/registry/consul/ConsulHealthEntryFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.registry.consul; 14 | 15 | import org.junit.Test; 16 | 17 | import java.io.IOException; 18 | import java.util.List; 19 | import java.util.Scanner; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | public class ConsulHealthEntryFactoryTest { 24 | 25 | private ConsulHealthEntryFactory factory = new ConsulHealthEntryFactory(); 26 | 27 | @Test 28 | public void testParsing() throws IOException { 29 | String input = loadResourceAsString("rating-full.json"); 30 | List entries = factory.parse(input); 31 | assertThat(entries).hasSize(145); 32 | entries = ConsulHealthEntryFilter.filterHealthyInstances(entries); 33 | assertThat(entries).hasSize(3); 34 | } 35 | 36 | public String loadResourceAsString(String fileName) throws IOException { 37 | String retval = null; 38 | try (Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(fileName))) { 39 | retval = scanner.useDelimiter("\\A").next(); 40 | } 41 | return retval; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/squareup/wire/schema/internal/parser/RpcMethodDefinition.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.squareup.wire.schema.internal.parser; 14 | 15 | import java.io.File; 16 | 17 | public class RpcMethodDefinition { 18 | 19 | protected String methodName; 20 | protected String requestType; 21 | protected String responseType; 22 | protected String packageName; 23 | protected String sourceFileName; 24 | 25 | public RpcMethodDefinition(String methodName, String requestType, String responseType, String packageName, String sourceFileName) { 26 | this.methodName = methodName; 27 | this.requestType = requestType; 28 | this.responseType = responseType; 29 | this.packageName = packageName; 30 | this.sourceFileName = sourceFileName; 31 | } 32 | 33 | public String getMethodName() { 34 | return methodName; 35 | } 36 | 37 | public String getRequestType() { 38 | return requestType; 39 | } 40 | 41 | public String getResponseType() { 42 | return responseType; 43 | } 44 | 45 | public String getPackageName() { 46 | return packageName; 47 | } 48 | 49 | public String getSourceFileName() { 50 | return sourceFileName; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/injection/ConsumerFactoryProvider.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.injection; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.Injector; 5 | import com.google.inject.Provider; 6 | import com.sixt.service.framework.ServiceProperties; 7 | import com.sixt.service.framework.kafka.messaging.ConsumerFactory; 8 | import com.sixt.service.framework.kafka.messaging.ReflectionTypeDictionaryFactory; 9 | import com.sixt.service.framework.kafka.messaging.TypeDictionary; 10 | import com.sixt.service.framework.metrics.MetricBuilderFactory; 11 | import io.opentracing.Tracer; 12 | 13 | public class ConsumerFactoryProvider implements Provider { 14 | 15 | private final Injector injector; 16 | private final ServiceProperties serviceProperties; 17 | private final Tracer tracer; 18 | private final MetricBuilderFactory metricBuilderFactory; 19 | 20 | private TypeDictionary typeDictionary = null; 21 | 22 | @Inject 23 | public ConsumerFactoryProvider(Injector injector, ServiceProperties serviceProperties, Tracer tracer, MetricBuilderFactory metricBuilderFactory) { 24 | this.injector = injector; 25 | this.serviceProperties = serviceProperties; 26 | this.tracer = tracer; 27 | this.metricBuilderFactory = metricBuilderFactory; 28 | } 29 | 30 | @Override 31 | public ConsumerFactory get() { 32 | if(typeDictionary == null) { 33 | // Thread safety: no need for volatile / synchronization - it doesn't hurt much if the TypeDictionary is created multiple times. 34 | ReflectionTypeDictionaryFactory dictionaryFactory = new ReflectionTypeDictionaryFactory(injector); 35 | typeDictionary = dictionaryFactory.createFromClasspath(); 36 | } 37 | 38 | return new ConsumerFactory(serviceProperties, typeDictionary, tracer, metricBuilderFactory); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/LoadBalancerUpdate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class LoadBalancerUpdate { 19 | 20 | protected List newServices = new ArrayList<>(); 21 | protected List updatedServices = new ArrayList<>(); 22 | protected List deletedServices = new ArrayList<>(); 23 | 24 | public void addNewService(ServiceEndpoint ep) { 25 | newServices.add(ep); 26 | } 27 | 28 | public void addUpdatedService(ServiceEndpoint ep) { 29 | updatedServices.add(ep); 30 | } 31 | 32 | public void addDeletedService(ServiceEndpoint ep) { 33 | deletedServices.add(ep); 34 | } 35 | 36 | public List getNewServices() { 37 | return newServices; 38 | } 39 | 40 | public List getUpdatedServices() { 41 | return updatedServices; 42 | } 43 | 44 | public List getDeletedServices() { 45 | return deletedServices; 46 | } 47 | 48 | public boolean isEmpty() { 49 | return newServices.isEmpty() && updatedServices.isEmpty() && 50 | deletedServices.isEmpty(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/jetty/JettyComposer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.jetty; 14 | 15 | import com.google.inject.servlet.GuiceFilter; 16 | import org.eclipse.jetty.jmx.MBeanContainer; 17 | import org.eclipse.jetty.server.Server; 18 | import org.eclipse.jetty.servlet.DefaultServlet; 19 | import org.eclipse.jetty.servlet.ServletContextHandler; 20 | import org.eclipse.jetty.util.log.Log; 21 | 22 | import javax.servlet.DispatcherType; 23 | import java.lang.management.ManagementFactory; 24 | import java.util.EnumSet; 25 | 26 | //TODO: add support for gzip compression 27 | public class JettyComposer { 28 | 29 | public static void compose(Server server) { 30 | //Servlets + Guice 31 | ServletContextHandler servletContextHandler = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); 32 | servletContextHandler.addFilter(GuiceFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); 33 | servletContextHandler.addServlet(DefaultServlet.class, "/"); 34 | 35 | //JMX stuff... 36 | MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); 37 | server.addEventListener(mbContainer); 38 | server.addBean(mbContainer); 39 | server.addBean(Log.getLog()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/SixtPartitioner.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.kafka; 2 | 3 | import net.jpountz.xxhash.XXHash32; 4 | import net.jpountz.xxhash.XXHashFactory; 5 | import org.apache.kafka.clients.producer.Partitioner; 6 | import org.apache.kafka.common.Cluster; 7 | import org.apache.kafka.common.PartitionInfo; 8 | import org.apache.kafka.common.utils.Utils; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | /** 15 | * We discovered that the default partition strategy in the java client is 16 | * different than what is used in librdkafka, which is used by several other 17 | * clients, so we now have aligned on using the xxHash strategy. 18 | */ 19 | public class SixtPartitioner implements Partitioner { 20 | 21 | private final XXHash32 xxHasher; 22 | private final int SEED = 0x9747b28c; 23 | private final AtomicInteger roundRobin = new AtomicInteger(0); 24 | 25 | public SixtPartitioner() { 26 | XXHashFactory factory = XXHashFactory.fastestInstance(); 27 | xxHasher = factory.hash32(); 28 | } 29 | 30 | @Override 31 | public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { 32 | List partitions = cluster.partitionsForTopic(topic); 33 | int numPartitions = partitions.size(); 34 | if (keyBytes == null) { 35 | int nextValue = roundRobin.getAndIncrement(); 36 | return Utils.toPositive(nextValue) % numPartitions; 37 | } else { 38 | // hash the keyBytes to choose a partition 39 | return Utils.toPositive(xxHasher.hash(keyBytes, 0, keyBytes.length, SEED)) % numPartitions; 40 | } 41 | } 42 | 43 | @Override 44 | public void close() { 45 | } 46 | 47 | @Override 48 | public void configure(Map configs) { 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/RpcClientFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import com.google.inject.Inject; 16 | import com.google.inject.Injector; 17 | import com.google.inject.Singleton; 18 | import com.google.protobuf.Message; 19 | 20 | /** 21 | * The starting point for getting an RpcClient to interact with remote services 22 | */ 23 | @Singleton 24 | public class RpcClientFactory { 25 | 26 | protected Injector injector; 27 | 28 | @Inject 29 | public RpcClientFactory(Injector injector) { 30 | this.injector = injector; 31 | } 32 | 33 | /** 34 | * Build an RpcClientBuilder. Use the withXXX methods to customize the behavior 35 | * of the client, then finish with builder.build(). 36 | */ 37 | public RpcClientBuilder newClient(String serviceName, String methodName, 38 | Class responseClass) { 39 | @SuppressWarnings("unchecked") 40 | RpcClientBuilder retval = (RpcClientBuilder) injector.getInstance(RpcClientBuilder.class); 41 | retval.setServiceName(serviceName); 42 | retval.setMethodName(methodName); 43 | retval.setResponseClass(responseClass); 44 | return retval; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/RpcCallExceptionDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import org.eclipse.jetty.client.api.ContentResponse; 16 | 17 | import java.io.IOException; 18 | import java.io.PrintWriter; 19 | import java.io.StringWriter; 20 | 21 | public interface RpcCallExceptionDecoder { 22 | 23 | RpcCallException decodeException(ContentResponse response) throws RpcCallException; 24 | 25 | static String exceptionToString(Throwable ex) { 26 | if (ex == null) { 27 | return "null"; 28 | } 29 | StringWriter str = new StringWriter(); 30 | str.append("Exception: ").append(ex.getClass().getSimpleName()); 31 | str.append(" Message: ").append(ex.getMessage()); 32 | str.append(" Stacktrace: "); 33 | Throwable cause = ex.getCause(); 34 | if (cause != null) { 35 | str.append("\nCause: ").append(exceptionToString(cause)); 36 | } 37 | PrintWriter writer = new PrintWriter(str); 38 | try { 39 | ex.printStackTrace(writer); 40 | return str.getBuffer().toString(); 41 | } finally { 42 | try { 43 | str.close(); 44 | writer.close(); 45 | } catch (IOException e) {} 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/SimpleRetryDelayer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import java.util.concurrent.atomic.AtomicLong; 16 | 17 | /** 18 | * Delay with a fixed interval up to a maximum total delay. 19 | *

20 | * The maximum total delay is simply the sum of all delays, but not the real time spend in delaying. 21 | */ 22 | public final class SimpleRetryDelayer implements RetryDelayer { 23 | 24 | private final long delayIntervallMillis; 25 | private final long maximumDelayMillis; 26 | 27 | private final AtomicLong accumulatedDelay = new AtomicLong(0); 28 | 29 | public SimpleRetryDelayer(long delayIntervallMillis, long maximumDelayMillis) { 30 | this.delayIntervallMillis = delayIntervallMillis; 31 | this.maximumDelayMillis = maximumDelayMillis; 32 | } 33 | 34 | 35 | @Override 36 | public boolean delay() { 37 | long total = accumulatedDelay.addAndGet(delayIntervallMillis); 38 | 39 | if (total >= maximumDelayMillis) { 40 | return false; 41 | } 42 | 43 | try { 44 | Thread.sleep(delayIntervallMillis); 45 | } catch (InterruptedException ignored) { 46 | 47 | } 48 | 49 | return true; 50 | } 51 | 52 | @Override 53 | public void reset() { 54 | accumulatedDelay.set(0); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/JsonRpcCallExceptionDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import com.google.gson.JsonElement; 16 | import com.google.gson.JsonObject; 17 | import com.google.gson.JsonParser; 18 | import org.eclipse.jetty.client.api.ContentResponse; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | public class JsonRpcCallExceptionDecoder implements RpcCallExceptionDecoder { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(JsonRpcCallExceptionDecoder.class); 25 | 26 | @Override 27 | public RpcCallException decodeException(ContentResponse response) throws RpcCallException { 28 | try { 29 | if (response != null) { 30 | JsonObject json = (JsonObject) new JsonParser().parse(response.getContentAsString()); 31 | JsonElement error = json.get("error"); 32 | if (error != null) { 33 | return RpcCallException.fromJson(error.toString()); 34 | } 35 | } 36 | } catch (Exception ex) { 37 | logger.warn("Caught exception decoding protobuf response exception", ex); 38 | throw new RpcCallException(RpcCallException.Category.InternalServerError, 39 | RpcCallExceptionDecoder.exceptionToString(ex)); 40 | } 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/rpc/ProtobufRpcCallExceptionDecoderTest.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc; 2 | 3 | import com.google.common.net.MediaType; 4 | import junitparams.Parameters; 5 | import org.assertj.core.api.Assertions; 6 | import org.eclipse.jetty.client.HttpContentResponse; 7 | import org.eclipse.jetty.client.api.ContentResponse; 8 | import org.junit.Test; 9 | 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static com.google.common.net.MediaType.ANY_TYPE; 15 | import static com.sixt.service.framework.rpc.RpcCallException.Category.InternalServerError; 16 | import static java.nio.charset.StandardCharsets.UTF_8; 17 | import static org.assertj.core.api.Assertions.*; 18 | import static org.junit.Assert.*; 19 | 20 | public class ProtobufRpcCallExceptionDecoderTest { 21 | 22 | private final ProtobufRpcCallExceptionDecoder decoder = new ProtobufRpcCallExceptionDecoder(); 23 | 24 | @Test 25 | public void decodeException_nullContentPassed_InternalServerErrorShouldBeReturned() 26 | throws RpcCallException { 27 | byte[] content = null; 28 | RpcCallException exception = decoder.decodeException(response(content)); 29 | 30 | assertThat(exception.getCategory()).isEqualTo(InternalServerError); 31 | assertThat(exception.getMessage()).isEqualTo("Empty response received"); 32 | } 33 | 34 | @Test 35 | public void decodeException_zeroLengthContentPassed_InternalServerErrorShouldBeReturned() 36 | throws RpcCallException { 37 | byte[] content = new byte[0]; 38 | RpcCallException exception = decoder.decodeException(response(content)); 39 | 40 | assertThat(exception.getCategory()).isEqualTo(InternalServerError); 41 | assertThat(exception.getMessage()).isEqualTo("Empty response received"); 42 | } 43 | 44 | private ContentResponse response(byte[] content) { 45 | return new HttpContentResponse(null, content, ANY_TYPE.type(), UTF_8.name()); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/servicetest/eventing/EventUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.eventing; 14 | 15 | import com.google.gson.JsonObject; 16 | import org.junit.Test; 17 | 18 | import static com.sixt.service.framework.servicetest.eventing.EventUtils.*; 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | public class EventUtilsTest { 22 | 23 | private static final String EVENT_NAME = "event-name"; 24 | 25 | @Test 26 | public void getEventName_FromMeta() throws Exception { 27 | JsonObject event = new JsonObject(); 28 | JsonObject meta = new JsonObject(); 29 | meta.addProperty(NAME, EVENT_NAME); 30 | event.add(META, meta); 31 | 32 | String eventName = EventUtils.getEventName(event); 33 | 34 | assertThat(eventName).isEqualTo(EVENT_NAME); 35 | } 36 | 37 | @Test 38 | public void getEventName_FromEventType() throws Exception { 39 | JsonObject event = new JsonObject(); 40 | event.addProperty(EVENT_TYPE, EVENT_NAME); 41 | 42 | String eventName = getEventName(event); 43 | 44 | assertThat(eventName).isEqualTo(EVENT_NAME); 45 | } 46 | 47 | @Test 48 | public void getEventName_NoEventName_ReturnNull() throws Exception { 49 | String eventName = getEventName(new JsonObject()); 50 | 51 | assertThat(eventName).isNull(); 52 | } 53 | } -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/infrastructure/RandomEventPublisher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | package com.sixt.service.test_service.infrastructure; 13 | 14 | import com.google.inject.Inject; 15 | import com.google.inject.Singleton; 16 | import com.sixt.service.framework.kafka.KafkaPublisher; 17 | import com.sixt.service.framework.kafka.KafkaPublisherFactory; 18 | import com.sixt.service.framework.protobuf.ProtobufUtil; 19 | import com.sixt.service.test_service.api.TestServiceOuterClass; 20 | import org.apache.kafka.clients.producer.ProducerConfig; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.util.Map; 25 | 26 | @Singleton 27 | public class RandomEventPublisher { 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(RandomEventPublisher.class); 30 | 31 | private final KafkaPublisher publisher; 32 | 33 | @Inject 34 | public RandomEventPublisher(KafkaPublisherFactory factory) { 35 | Map props = factory.getDefaultProperties(); 36 | props.put(ProducerConfig.RETRIES_CONFIG, "3"); 37 | props.put(ProducerConfig.ACKS_CONFIG, "all"); 38 | publisher = factory.newBuilder("events", props).build(); 39 | } 40 | 41 | public void publishSync(TestServiceOuterClass.HandlerSuccessEvent event) { 42 | if (!publisher.publishSync(ProtobufUtil.protobufToJson(event).toString())) { 43 | logger.warn("Failed to publish event: {}", event); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/health/ReadinessCheckServer.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.health; 2 | 3 | import com.sixt.service.framework.FeatureFlags; 4 | import com.sixt.service.framework.ServiceProperties; 5 | import fi.iki.elonen.NanoHTTPD; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Singleton; 11 | import java.io.IOException; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | @Singleton 15 | public class ReadinessCheckServer { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(ReadinessCheckServer.class); 18 | 19 | private final AtomicBoolean isReady = new AtomicBoolean(false); 20 | private final ServiceProperties serviceProps; 21 | private NanoReadinessServer server; 22 | 23 | @Inject 24 | public ReadinessCheckServer(ServiceProperties serviceProperties) { 25 | this.serviceProps = serviceProperties; 26 | } 27 | 28 | public synchronized void serveRequests() { 29 | boolean wasStarted = isReady.getAndSet(true); 30 | if (! wasStarted) { 31 | int port = FeatureFlags.getReadinessCheckPort(serviceProps); 32 | if (port > 0) { 33 | server = new NanoReadinessServer(port); 34 | try { 35 | server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); 36 | } catch (IOException e) { 37 | logger.warn("Error starting readiness check server", e); 38 | } 39 | } 40 | } 41 | } 42 | 43 | private class NanoReadinessServer extends NanoHTTPD { 44 | 45 | public NanoReadinessServer(int port) { 46 | super(port); 47 | } 48 | 49 | @Override 50 | public Response serve(IHTTPSession session) { 51 | Response.Status statusCode = ReadinessCheckServer.this.isReady.get() ? 52 | Response.Status.OK : Response.Status.SERVICE_UNAVAILABLE; 53 | return newFixedLengthResponse(statusCode, "text/plain", ""); 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/logging/SixtJsonEncoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | import ch.qos.logback.classic.spi.ILoggingEvent; 16 | import net.logstash.logback.composite.JsonProvider; 17 | import net.logstash.logback.composite.loggingevent.LogLevelJsonProvider; 18 | import net.logstash.logback.composite.loggingevent.LoggingEventJsonProviders; 19 | import net.logstash.logback.encoder.LogstashEncoder; 20 | 21 | public class SixtJsonEncoder extends LogstashEncoder { 22 | 23 | @SuppressWarnings("unchecked") 24 | public SixtJsonEncoder() { 25 | LoggingEventJsonProviders providers = getFormatter().getProviders(); 26 | 27 | // Remove provider that is responsible for log level appending 28 | removeDefaultProvider(providers); 29 | 30 | // Register our implementation 31 | providers.addLogLevel(new CustomLogLevelJsonProvider()); 32 | 33 | providers.addProvider(new ServicePropertiesProvider()); 34 | } 35 | 36 | private void removeDefaultProvider(LoggingEventJsonProviders providers) { 37 | JsonProvider providerToDelete = null; 38 | 39 | for (JsonProvider provider : providers.getProviders()) { 40 | if (provider instanceof LogLevelJsonProvider) { 41 | providerToDelete = provider; 42 | break; 43 | } 44 | } 45 | 46 | providers.removeProvider(providerToDelete); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/ServiceDependencyHealthCheck.java: -------------------------------------------------------------------------------- 1 | package com.sixt.service.framework.rpc; 2 | 3 | import com.sixt.service.framework.annotation.HealthCheckProvider; 4 | import com.sixt.service.framework.health.HealthCheck; 5 | import com.sixt.service.framework.health.HealthCheckContributor; 6 | 7 | import javax.inject.Singleton; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.ConcurrentLinkedQueue; 11 | 12 | @HealthCheckProvider 13 | @Singleton 14 | public class ServiceDependencyHealthCheck implements HealthCheckContributor { 15 | 16 | private ConcurrentLinkedQueue endpoints = new ConcurrentLinkedQueue<>(); 17 | 18 | @Override 19 | public HealthCheck getHealthCheck() { 20 | List trippedBreakers = new ArrayList<>(); 21 | trippedBreakers.forEach(ep -> { 22 | if (CircuitBreakerState.isTripped(ep.getCircuitBreakerState())) { 23 | trippedBreakers.add(ep); 24 | } 25 | }); 26 | if (trippedBreakers.isEmpty()) { 27 | return new HealthCheck(); 28 | } else { 29 | return buildFailedHealthCheck(trippedBreakers); 30 | } 31 | } 32 | 33 | // It is unknown whether the dependency is desired or required, so we set to WARN 34 | private HealthCheck buildFailedHealthCheck(List trippedBreakers) { 35 | StringBuilder sb = new StringBuilder(); 36 | trippedBreakers.forEach(serviceEndpoint -> { 37 | sb.append(serviceEndpoint.getServiceName()).append('/') 38 | .append(serviceEndpoint.getAvailZone()).append('/') 39 | .append(serviceEndpoint.getHostAndPort()).append(' '); 40 | }); 41 | return new HealthCheck("RPC dependencies", HealthCheck.Status.WARN, sb.toString()); 42 | } 43 | 44 | @Override 45 | public boolean shouldRegister() { 46 | return true; 47 | } 48 | 49 | public void monitorServiceEndpoint(ServiceEndpoint serviceEndpoint) { 50 | endpoints.add(serviceEndpoint); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/AbstractServiceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | import com.google.protobuf.Message; 16 | import org.junit.Test; 17 | 18 | import java.io.PrintStream; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | public class AbstractServiceTest { 23 | 24 | @Test 25 | public void methodHandlerShouldBeRegistered() throws Exception { 26 | // given 27 | AbstractServiceMock service = new AbstractServiceMock(); 28 | service.initializeGuice(); 29 | 30 | // when 31 | service.registerMethodHandlerFor("Test.methodHandlerShouldBeRegistered", ServiceMethodHandlerMock.class); 32 | 33 | // then 34 | assertThat( 35 | ServiceMethodHandlerMock.class.getName().equals( 36 | service.getMethodHandlers().get("Test.methodHandlerShouldBeRegistered").getClass().getName() 37 | ) 38 | ); 39 | } 40 | 41 | private static class ServiceMethodHandlerMock implements ServiceMethodHandler { 42 | 43 | @Override 44 | public Message handleRequest(Message request, OrangeContext ctx) { 45 | return null; 46 | } 47 | 48 | } 49 | 50 | private class AbstractServiceMock extends AbstractService { 51 | 52 | @Override 53 | public void registerMethodHandlers() { } 54 | 55 | @Override 56 | public void displayHelp(PrintStream out) { } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/GoTimer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | import com.codahale.metrics.Metric; 16 | import com.codahale.metrics.Timer; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class GoTimer extends Timer implements Metric { 21 | 22 | //The structure of codahale metrics don't mesh with our go metrics. 23 | //To track failures and successes in different buckets requires using 24 | // multiple underlying Timer objects; 25 | protected Timer successTimer; 26 | protected Timer failureTimer; 27 | protected String name; 28 | 29 | public GoTimer(String name) { 30 | this.name = name; 31 | reset(); 32 | } 33 | 34 | public long start() { 35 | return System.nanoTime(); 36 | } 37 | 38 | public void recordSuccess(long startTime) { 39 | successTimer.update(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); 40 | } 41 | 42 | public void recordFailure(long startTime) { 43 | failureTimer.update(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); 44 | } 45 | 46 | public Timer getSuccessTimer() { 47 | return successTimer; 48 | } 49 | 50 | public Timer getFailureTimer() { 51 | return failureTimer; 52 | } 53 | 54 | public String getName() { 55 | return name; 56 | } 57 | 58 | public void reset() { 59 | successTimer = new Timer(); 60 | failureTimer = new Timer(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/ServiceEntryPoint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service; 14 | 15 | import com.sixt.service.framework.AbstractService; 16 | import com.sixt.service.framework.annotation.OrangeMicroservice; 17 | import com.sixt.service.framework.kafka.messaging.ConsumerFactory; 18 | import com.sixt.service.framework.kafka.messaging.DiscardFailedMessages; 19 | import com.sixt.service.test_service.handler.*; 20 | 21 | import java.io.PrintStream; 22 | 23 | @OrangeMicroservice 24 | public class ServiceEntryPoint extends AbstractService { 25 | 26 | @Override 27 | public void registerMethodHandlers() { 28 | registerMethodHandlerFor("TestService.GetRandomString", RandomHandler.class); 29 | registerPreMethodHandlerHookFor("TestService.GetRandomString", RandomHandlerPreHook.class); 30 | registerPostMethodHandlerHookFor("TestService.GetRandomString", RandomHandlerPostHook.class); 31 | registerMethodHandlerFor("TestService.SlowResponder", SlowRespondingHandler.class); 32 | } 33 | 34 | @Override 35 | public void displayHelp(PrintStream out) { 36 | } 37 | 38 | @Override 39 | public void bootstrapComplete() throws InterruptedException { 40 | // Start a messaging consumer for the default inbox. 41 | ConsumerFactory consumerFactory = injector.getInstance(ConsumerFactory.class); 42 | consumerFactory.defaultInboxConsumer(new DiscardFailedMessages()); 43 | 44 | injector.getInstance(RandomEventHandler.class); 45 | 46 | super.bootstrapComplete(); 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/logging/SixtLogbackContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | import ch.qos.logback.classic.Level; 16 | import ch.qos.logback.classic.LoggerContext; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.util.List; 21 | 22 | public class SixtLogbackContext implements LoggingContext { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(SixtLogbackContext.class); 25 | 26 | public void updateLoggerLogLevel(String newLevel) { 27 | this.updateLoggerLogLevel("com.sixt", newLevel); 28 | } 29 | 30 | public void updateLoggerLogLevel(String loggerName, String newLevel) { 31 | SixtLogbackContext.logger.info("Starting to update Logger's log level to {}", newLevel); 32 | 33 | List loggers 34 | = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLoggerList(); 35 | for (ch.qos.logback.classic.Logger logger : loggers) { 36 | String name = logger.getName(); 37 | Level level = logger.getLevel(); 38 | SixtLogbackContext.logger.debug("Logger [{}] has log level {}", name, level); 39 | if (name.equals(loggerName)) { 40 | logger.setLevel(Level.toLevel(newLevel)); 41 | SixtLogbackContext.logger.info("Affected Loggers were [{}] from {} to {}", logger, level, newLevel); 42 | 43 | return; // child loggers will be handled by logback itself 44 | } 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/RpcClientMetrics.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | import com.sixt.service.framework.metrics.GoTimer; 18 | import com.sixt.service.framework.metrics.MetricBuilderFactory; 19 | import org.apache.commons.lang3.StringUtils; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | @Singleton 24 | public class RpcClientMetrics { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(RpcClientMetrics.class); 27 | 28 | public final static String UNKNOWN = "unknown"; 29 | 30 | private final MetricBuilderFactory metricBuilderFactory; 31 | 32 | @Inject 33 | public RpcClientMetrics(MetricBuilderFactory metricBuilderFactory) { 34 | this.metricBuilderFactory = metricBuilderFactory; 35 | } 36 | 37 | 38 | public synchronized GoTimer getMethodTimer(String destinationService, 39 | String destinationMethod) { 40 | if (StringUtils.isBlank(destinationService)) { 41 | destinationService = UNKNOWN; 42 | } 43 | if (StringUtils.isBlank(destinationMethod)) { 44 | destinationMethod = UNKNOWN; 45 | } 46 | return metricBuilderFactory.newMetric("client_rpc"). 47 | withTag("method", UNKNOWN). 48 | withTag("destination_service", destinationService). 49 | withTag("destination_method", destinationMethod).buildTimer(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /sit/src/serviceTest/resources/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | com.sixt.service.test-service: 5 | build: 6 | context: ../../../build/docker 7 | dockerfile: Dockerfile 8 | environment: 9 | - "kafkaServer=kafka:9092" 10 | - "logLevel=DEBUG" 11 | - "registry=consul" 12 | - "servicePort=40000" 13 | - "readinessCheckPort=21000" 14 | entrypoint: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -server -Dfile.encoding=UTF8 -XX:-OmitStackTraceInFastThrow -Djava.security.egd=file:/dev/./urandom -jar /app.jar -registryServer consul:8500 15 | ports: 16 | - "5005" 17 | - "40000" 18 | - "42000-42001" 19 | depends_on: 20 | zookeeper: 21 | condition: service_started 22 | consul: 23 | condition: service_started 24 | kafka: 25 | condition: service_healthy 26 | 27 | consul: 28 | image: consul:0.7.5 29 | command: "agent -server -bootstrap -data-dir=/tmp -client=0.0.0.0 -ui" 30 | ports: 31 | - "8500" 32 | hostname: consul 33 | 34 | zookeeper: 35 | image: wurstmeister/zookeeper:3.4.6 36 | ports: 37 | - "2181" 38 | hostname: zookeeper 39 | 40 | kafka: 41 | image: wurstmeister/kafka:0.10.1.0 42 | ports: 43 | - "9092" 44 | environment: 45 | KAFKA_ADVERTISED_PORT: 9092 46 | KAFKA_CREATE_TOPICS: "inbox_test:3:1,inbox-com.sixt.service.test-service:3:1,message-count:3:1,events.RandomTopic:3:1,events:3:1" # events must be last for the healthcheck to work 47 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 48 | KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" 49 | volumes: 50 | - /var/run/docker.sock:/var/run/docker.sock 51 | - ./scripts/start_kafka.sh:/tmp/start_kafka.sh 52 | depends_on: 53 | - "zookeeper" 54 | entrypoint: /tmp/start_kafka.sh 55 | hostname: kafka 56 | healthcheck: 57 | # nota bene: test command is executed inside the container 58 | test: ["CMD-SHELL", "/opt/kafka_2.11-0.10.1.0/bin/kafka-topics.sh --zookeeper zookeeper:2181 --list | grep 'events$$' "] 59 | interval: 10s # test is executed every 10s 60 | timeout: 10s # timeout per test cmd 61 | retries: 12 # try 12*10s = 120s = 2min -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/logging/CustomLogLevelJsonProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | import ch.qos.logback.classic.Level; 16 | import ch.qos.logback.classic.spi.ILoggingEvent; 17 | import com.fasterxml.jackson.core.JsonGenerator; 18 | import net.logstash.logback.composite.JsonWritingUtils; 19 | import net.logstash.logback.composite.loggingevent.LogLevelJsonProvider; 20 | 21 | import java.io.IOException; 22 | 23 | public class CustomLogLevelJsonProvider extends LogLevelJsonProvider { 24 | @Override 25 | public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException { 26 | String level; 27 | switch (event.getLevel().toInt()) { 28 | case Level.ERROR_INT: 29 | level = "error"; 30 | break; 31 | case Level.WARN_INT: 32 | level = "warning"; 33 | break; 34 | case Level.INFO_INT: 35 | level = "info"; 36 | break; 37 | case Level.DEBUG_INT: 38 | level = "debug"; 39 | break; 40 | case Level.TRACE_INT: 41 | level = "trace"; 42 | break; 43 | case Level.ALL_INT: 44 | level = "all"; 45 | break; 46 | case Level.OFF_INT: 47 | level = "off"; 48 | break; 49 | default: 50 | level = "???"; 51 | } 52 | JsonWritingUtils.writeStringField( 53 | generator, getFieldName(), level); 54 | } 55 | } -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/handler/RandomHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service.handler; 14 | 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | import com.sixt.service.framework.OrangeContext; 18 | import com.sixt.service.framework.ServiceMethodHandler; 19 | import com.sixt.service.framework.rpc.RpcCallException; 20 | import com.sixt.service.test_service.api.TestServiceOuterClass.GetRandomStringQuery; 21 | import com.sixt.service.test_service.api.TestServiceOuterClass.RandomStringResponse; 22 | import com.sixt.service.test_service.infrastructure.RandomEventPublisher; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import java.util.UUID; 27 | 28 | @Singleton 29 | public class RandomHandler implements ServiceMethodHandler { 30 | 31 | private static final Logger logger = LoggerFactory.getLogger(RandomHandler.class); 32 | private final RandomEventPublisher publisher; 33 | 34 | @Inject 35 | public RandomHandler(RandomEventPublisher publisher) { 36 | this.publisher = publisher; 37 | } 38 | 39 | @Override 40 | public RandomStringResponse handleRequest(GetRandomStringQuery request, OrangeContext ctx) throws RpcCallException { 41 | logger.info("Received GetRandomStringQuery"); 42 | String randomUUID = UUID.randomUUID().toString(); 43 | String response = request.getInput() + randomUUID; 44 | logger.info("Response for GetRandomStringQuery: {}", response); 45 | return RandomStringResponse.newBuilder().setRandom(response).build(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/servicetest/service/ServiceUnderTestLoadBalancer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.service; 14 | 15 | import com.google.inject.Inject; 16 | import com.sixt.service.framework.rpc.CircuitBreakerState; 17 | import com.sixt.service.framework.rpc.ServiceDependencyHealthCheck; 18 | import com.sixt.service.framework.rpc.ServiceEndpoint; 19 | import com.sixt.service.framework.servicetest.helper.DockerPortResolver; 20 | import com.sixt.service.framework.servicetest.mockservice.ServiceImpersonatorLoadBalancer; 21 | 22 | import java.util.concurrent.ScheduledThreadPoolExecutor; 23 | 24 | public class ServiceUnderTestLoadBalancer extends ServiceImpersonatorLoadBalancer { 25 | 26 | @Inject 27 | public ServiceUnderTestLoadBalancer(DockerPortResolver dockerPortResolver, 28 | ServiceDependencyHealthCheck dependencyHealthCheck) { 29 | super(dockerPortResolver, dependencyHealthCheck); 30 | } 31 | 32 | @Override 33 | protected int locateTargetServicePort() { 34 | return dockerPortResolver.getExposedPortFromDocker(serviceName, 40000); 35 | } 36 | 37 | @Override 38 | public ServiceEndpoint getHealthyInstance() { 39 | return new ServiceEndpoint("localhost:" + locateTargetServicePort(), "", 40 | new CircuitBreakerState(new MockScheduledThreadPoolExecutor(0))); 41 | } 42 | 43 | private static class MockScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { 44 | 45 | public MockScheduledThreadPoolExecutor(int corePoolSize) { 46 | super(0); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/squareup/wire/schema/internal/parser/SixtProtoParserTest.java: -------------------------------------------------------------------------------- 1 | package com.squareup.wire.schema.internal.parser; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | 9 | public class SixtProtoParserTest { 10 | 11 | private PackageMatcherTestParser testParser; 12 | 13 | @Test 14 | public void positive_case() { 15 | testParser = new PackageMatcherTestParser("com.sixt.service.payment"); 16 | testParser.should_match("com.sixt.service.payment"); 17 | testParser.should_match("com.sixt.service.payment.API"); 18 | 19 | testParser = new PackageMatcherTestParser("com.sixt.service.rating"); 20 | testParser.should_match("com.sixt.service.rating"); 21 | testParser.should_match("com.sixt.service.rating.api"); 22 | 23 | testParser = new PackageMatcherTestParser("com.sixt.service.accounting"); 24 | testParser.should_match("com.sixt.service.accounting.api"); 25 | testParser.should_match("com.sixt.service.accounting"); 26 | } 27 | 28 | @Test 29 | public void negative_case() { 30 | testParser = new PackageMatcherTestParser("com.sixt.service.payment"); 31 | testParser.should_not_match("com.sixt.service.payment_authorization"); 32 | 33 | testParser = new PackageMatcherTestParser("com.sixt.service.rating"); 34 | testParser.should_not_match("org.sixt.service.rating_payment"); 35 | 36 | testParser = new PackageMatcherTestParser("com.sixt.service.accounting"); 37 | testParser.should_not_match("com.test.service.accounting_payment"); 38 | } 39 | 40 | class PackageMatcherTestParser 41 | extends SixtProtoParser { 42 | 43 | PackageMatcherTestParser(final String service) { 44 | super("test.txt", new File("test.txt"), service); 45 | } 46 | 47 | void should_match(String pack) { 48 | assertThat(matchesServiceName(pack)).isTrue() 49 | .as(String.format("%s should match with %s", serviceName, pack)); 50 | } 51 | 52 | void should_not_match(String pack) { 53 | assertThat(matchesServiceName(pack)).isFalse() 54 | .as(String.format("%s should not match with %s", serviceName, pack)); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/logging/SixtJsonEncoderTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.logging; 14 | 15 | import ch.qos.logback.classic.spi.ILoggingEvent; 16 | import net.logstash.logback.composite.JsonProvider; 17 | import net.logstash.logback.composite.JsonProviders; 18 | import net.logstash.logback.composite.loggingevent.LogLevelJsonProvider; 19 | import org.junit.Test; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import static net.logstash.logback.argument.StructuredArguments.keyValue; 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class SixtJsonEncoderTest { 27 | 28 | @Test 29 | public void overrideLogLevelProvider() { 30 | SixtJsonEncoder encoder = new SixtJsonEncoder(); 31 | JsonProviders providers = encoder.getProviders(); 32 | int defaultCount = 0; 33 | int overrideCount = 0; 34 | 35 | for (JsonProvider provider : providers.getProviders()) { 36 | if (provider instanceof LogLevelJsonProvider && 37 | !(provider instanceof CustomLogLevelJsonProvider)) { 38 | defaultCount++; 39 | } else if (provider instanceof CustomLogLevelJsonProvider) { 40 | overrideCount++; 41 | } 42 | } 43 | assertThat(defaultCount).isEqualTo(0); 44 | assertThat(overrideCount).isEqualTo(1); 45 | } 46 | 47 | private static final Logger logger = LoggerFactory.getLogger(SixtJsonEncoderTest.class); 48 | 49 | @Test 50 | public void extraJsonField() { 51 | logger.error("This is a test", keyValue("category", "traffic")); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/ProtobufRpcCallExceptionDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import com.sixt.service.framework.protobuf.ProtobufRpcResponse; 16 | import org.apache.commons.lang3.ArrayUtils; 17 | import org.eclipse.jetty.client.api.ContentResponse; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | public class ProtobufRpcCallExceptionDecoder implements RpcCallExceptionDecoder { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(ProtobufRpcCallExceptionDecoder.class); 24 | 25 | @Override 26 | public RpcCallException decodeException(ContentResponse response) throws RpcCallException { 27 | try { 28 | if (response != null) { 29 | byte[] data = response.getContent(); 30 | if (ArrayUtils.isEmpty(data)) { 31 | logger.warn("Unable to decode: empty response received"); 32 | return new RpcCallException(RpcCallException.Category.InternalServerError, 33 | "Empty response received"); 34 | } 35 | ProtobufRpcResponse pbResponse = new ProtobufRpcResponse(data); 36 | String error = pbResponse.getErrorMessage(); 37 | if (error != null) { 38 | return RpcCallException.fromJson(error); 39 | } 40 | } 41 | } catch (Exception ex) { 42 | logger.warn("Caught exception decoding protobuf response exception", ex); 43 | throw new RpcCallException(RpcCallException.Category.InternalServerError, 44 | RpcCallExceptionDecoder.exceptionToString(ex)); 45 | } 46 | return null; 47 | } 48 | 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/MessageType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | import java.lang.reflect.Type; 16 | 17 | /** 18 | * Value object to represent the type of a (protobuf) message. 19 | * Used to specify the type of the inner message in the messaging envelope. 20 | *

21 | * Conventions: 22 | * - The type name is the Java Type.getTypeName() of the generated protobuf message. 23 | *

24 | * - In the defining proto file 25 | * -- specify option java_multiple_files = true; to avoid the OuterClass that nests the message types. 26 | * -- use package com.sixt.service.{SERVICENAME}.api; as default namespace/package for the asynchronous messaging contract 27 | * -- there is no need for the option java_package directive 28 | */ 29 | public final class MessageType { 30 | 31 | private final String type; 32 | 33 | MessageType(String typeName) { 34 | type = typeName; 35 | } 36 | 37 | static MessageType of(com.google.protobuf.Message protoMessage) { 38 | return new MessageType(protoMessage.getClass().getTypeName()); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return type; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) return true; 49 | if (o == null || getClass() != o.getClass()) return false; 50 | 51 | MessageType that = (MessageType) o; 52 | 53 | return type != null ? type.equals(that.type) : that.type == null; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return type != null ? type.hashCode() : 0; 59 | } 60 | 61 | public static MessageType of(Type t) { 62 | return new MessageType(t.getTypeName()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/json/JsonUtilTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.json; 14 | 15 | import com.google.gson.JsonObject; 16 | import com.google.gson.JsonParser; 17 | import org.assertj.core.data.Percentage; 18 | import org.junit.Test; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | public class JsonUtilTest { 23 | 24 | private JsonUtil jsonUtil = new JsonUtil(); 25 | 26 | @Test 27 | public void extractFieldsTest() { 28 | String event = "{\"meta\":{\"name\":\"OneVehicleDynamicData\",\"timestamp\":" + 29 | "\"2017-02-14T10:20:37.629Z\",\"grouping\":\"OneVehicleFinderData\"," + 30 | "\"distribution_key\":\"665292e8-6a3b-4702-9666-b5624d6c8320\"},\"position\":{" + 31 | "\"latitude\":48.042999267578125,\"longitude\":11.510173797607422,\"foo\":7}," + 32 | "\"vehicle_id\":\"abcd\",\"fuel_level\":999," + 33 | "\"charge_level\":50.0,\"odometer\":12345}"; 34 | JsonObject json = (JsonObject) new JsonParser().parse(event); 35 | assertThat(jsonUtil.extractInteger(json, "odometer", -1)).isEqualTo(12345); 36 | assertThat(jsonUtil.extractDouble(json, "charge_level", -1)) 37 | .isCloseTo(50, Percentage.withPercentage(1)); 38 | assertThat(jsonUtil.extractString(json, "vehicle_id")).isEqualTo("abcd"); 39 | 40 | //check dot-notation paths 41 | assertThat(jsonUtil.extractInteger(json, "position.foo", -1)).isEqualTo(7); 42 | assertThat(jsonUtil.extractDouble(json, "position.latitude", 0)) 43 | .isCloseTo(48.043, Percentage.withPercentage(1)); 44 | assertThat(jsonUtil.extractString(json, "meta.name")).isEqualTo("OneVehicleDynamicData"); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/AbstractServiceTest_initializeGuice.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework; 14 | 15 | import com.google.inject.Module; 16 | import org.junit.Test; 17 | 18 | import java.io.PrintStream; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | public class AbstractServiceTest_initializeGuice { 25 | 26 | @Test 27 | public void initializeGuice_ProvideImmutableListViaExtendingService_NoProblem() throws Exception { 28 | // given 29 | final AbstractService service = new AbstractService() { 30 | @Override 31 | public void displayHelp(PrintStream out) { 32 | 33 | } 34 | 35 | @Override 36 | protected List getGuiceModules() { 37 | return Collections.emptyList(); 38 | } 39 | }; 40 | 41 | // when 42 | service.initializeGuice(); 43 | 44 | // then 45 | // no exception should be raised 46 | assertThat(service.injector).isNotNull(); 47 | } 48 | 49 | @Test 50 | public void initializeGuice_ProvideNullListViaExtendingService_NoProblem() throws Exception { 51 | // given 52 | final AbstractService service = new AbstractService() { 53 | @Override 54 | public void displayHelp(PrintStream out) { 55 | 56 | } 57 | 58 | @Override 59 | protected List getGuiceModules() { 60 | return null; 61 | } 62 | }; 63 | 64 | // when 65 | service.initializeGuice(); 66 | 67 | // then 68 | // no exception should be raised 69 | assertThat(service.injector).isNotNull(); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/json/JsonRpcRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.json; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.JsonElement; 17 | import com.google.gson.JsonNull; 18 | import com.google.gson.JsonObject; 19 | 20 | public class JsonRpcRequest { 21 | 22 | public final static String ID_FIELD = "id"; 23 | public final static String METHOD_FIELD = "method"; 24 | public final static String PARAMS_FIELD = "params"; 25 | 26 | private JsonElement id; 27 | private String method; 28 | private JsonArray params; 29 | 30 | public JsonRpcRequest(JsonElement id, String method, JsonArray params) { 31 | setId(id); 32 | setMethod(method); 33 | setParams(params); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | JsonObject json = new JsonObject(); 39 | json.add(ID_FIELD, id); 40 | json.addProperty(METHOD_FIELD, method); 41 | json.add(PARAMS_FIELD, params); 42 | return json.toString(); 43 | } 44 | 45 | public JsonElement getId() { 46 | return id; 47 | } 48 | 49 | public void setId(JsonElement id) { 50 | this.id = id; 51 | } 52 | 53 | public String getMethod() { 54 | return method; 55 | } 56 | 57 | public void setMethod(String method) { 58 | this.method = method; 59 | } 60 | 61 | public JsonArray getParams() { 62 | return params; 63 | } 64 | 65 | public void setParams(JsonArray params) { 66 | this.params = params; 67 | } 68 | 69 | public String getIdAsString() { 70 | if (id == null || id instanceof JsonNull) { 71 | return null; 72 | } else { 73 | return id.getAsString(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/servicetest/mockservice/ImpersonatedPortDictionary.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.mockservice; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | public class ImpersonatedPortDictionary { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(ImpersonatedPortDictionary.class); 24 | 25 | //Note: I hate static singletons, but since ServiceUnderTest and each impersonator 26 | // need their own injection stacks, so it's difficult not to use statics here 27 | 28 | private static ImpersonatedPortDictionary instance; 29 | private int nextPort = 42000; 30 | private Map ports = new HashMap<>(); 31 | 32 | private ImpersonatedPortDictionary() { 33 | } 34 | 35 | public synchronized static ImpersonatedPortDictionary getInstance() { 36 | if (instance == null) { 37 | instance = new ImpersonatedPortDictionary(); 38 | } 39 | return instance; 40 | } 41 | 42 | public synchronized int getInternalPortForImpersonated(String service) { 43 | Integer retval = ports.get(service); 44 | if (retval == null) { 45 | throw new IllegalStateException("Service " + service + " is unknown. Was it registered correctly?"); 46 | } 47 | logger.debug("Returning port {} for service {}", retval, service); 48 | return retval; 49 | } 50 | 51 | public synchronized int newInternalPortForImpersonated(String service) { 52 | int retval = nextPort; 53 | ports.put(service, retval); 54 | nextPort++; 55 | logger.debug("Using port {} for service {}", retval, service); 56 | return retval; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/rpc/LoadBalancer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Ja-micro has a client-side load balancer implementation bound by default. (LoadBalancerImpl) 19 | * This interface allows one to provide a different implementation. 20 | */ 21 | public interface LoadBalancer { 22 | 23 | /** 24 | * There is one LoadBalancer per service. Set the service name for this instance. 25 | */ 26 | void setServiceName(String serviceName); 27 | 28 | String getServiceName(); 29 | 30 | /** 31 | * Used by the RpcClient to get a wrapper that directs calls to a specific instance 32 | * of a called service based on health. 33 | */ 34 | HttpClientWrapper getHttpClientWrapper(); 35 | 36 | /** 37 | * Get an instance of a called service that is likely to be healthy (more specifically: 38 | * an instance that is not known to not be healthy, unless it is a probe to allow the instance 39 | * to be healthy again.) 40 | */ 41 | ServiceEndpoint getHealthyInstance(); 42 | 43 | /** 44 | * Like above, but excluding ServiceEndpoints that the client has already tried. 45 | * Influenced by FeatureFlags.shouldDisableRpcInstanceRetry 46 | */ 47 | ServiceEndpoint getHealthyInstanceExclude(List triedEndpoints); 48 | 49 | /** 50 | * Pause the calling thread until an apparently healthy instance appears through 51 | * service discovery. 52 | */ 53 | void waitForServiceInstance(); 54 | 55 | /** 56 | * Used by the service registry watcher to let the LoadBalancer know about changes to 57 | * service instances based on registry information. 58 | */ 59 | void updateServiceEndpoints(LoadBalancerUpdate updates); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/registry/consul/ConsulServiceRegistryPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.registry.consul; 14 | 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | import com.sixt.service.framework.MethodHandlerDictionary; 18 | import com.sixt.service.framework.annotation.ServiceRegistryPlugin; 19 | import com.sixt.service.framework.registry.ServiceDiscoveryProvider; 20 | import com.sixt.service.framework.registry.ServiceRegistrationProvider; 21 | import com.sixt.service.framework.rpc.LoadBalancer; 22 | 23 | /** 24 | * Sixt implementation of a ServiceRegistryPlugin for Hashicorp's Consul. 25 | * https://www.consul.io/ 26 | */ 27 | 28 | @Singleton 29 | @ServiceRegistryPlugin(name = "consul") 30 | public class ConsulServiceRegistryPlugin implements 31 | ServiceDiscoveryProvider, ServiceRegistrationProvider { 32 | 33 | private final RegistrationMonitor registrationMonitor; 34 | private final RegistrationManager registrationManager; 35 | 36 | @Inject 37 | public ConsulServiceRegistryPlugin(RegistrationMonitor registrationMonitor, 38 | RegistrationManager registrationManager) { 39 | this.registrationMonitor = registrationMonitor; 40 | this.registrationManager = registrationManager; 41 | } 42 | 43 | @Override 44 | public void monitorService(LoadBalancer lb) { 45 | registrationMonitor.monitorService(lb); 46 | } 47 | 48 | @Override 49 | public void initialize(MethodHandlerDictionary methodHandlers) { 50 | registrationManager.setRegisteredHandlers(methodHandlers.getMethodHandlers()); 51 | registrationManager.register(); 52 | } 53 | 54 | @Override 55 | public void shutdown() { 56 | registrationMonitor.shutdown(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/json/JsonRpcRequestTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.json; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.JsonElement; 17 | import com.google.gson.JsonNull; 18 | import com.google.gson.JsonPrimitive; 19 | import org.junit.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | public class JsonRpcRequestTest { 24 | 25 | @Test 26 | public void gettersAndSetters() { 27 | JsonArray array = new JsonArray(); 28 | JsonRpcRequest req = new JsonRpcRequest(null, "mefod", array); 29 | assertThat(req.getMethod()).isEqualTo("mefod"); 30 | assertThat(req.getParams()).isEqualTo(array); 31 | } 32 | 33 | @Test 34 | public void getIdAsString_SomeIdAsNumber_TheNumberAsString() { 35 | // given 36 | JsonElement jsonElement = new JsonPrimitive(20); 37 | JsonRpcRequest req = new JsonRpcRequest(jsonElement, "something", null); 38 | 39 | // when 40 | String result = req.getIdAsString(); 41 | 42 | // then 43 | assertThat(result).isNotNull(); 44 | assertThat(result).isEqualTo("20"); 45 | } 46 | 47 | @Test 48 | public void getIdAsString_NullId_Null() { 49 | // given 50 | JsonRpcRequest req = new JsonRpcRequest(null, "something", null); 51 | 52 | // when 53 | String result = req.getIdAsString(); 54 | 55 | // then 56 | assertThat(result).isNull(); 57 | } 58 | 59 | @Test 60 | public void getIdAsString_NullJson_Null() { 61 | // given 62 | JsonRpcRequest req = new JsonRpcRequest(JsonNull.INSTANCE, "something", null); 63 | 64 | // when 65 | String result = req.getIdAsString(); 66 | 67 | // then 68 | assertThat(result).isNull(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/servicetest/injection/ServiceImpersonatorModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.servicetest.injection; 14 | 15 | import com.sixt.service.framework.ServiceProperties; 16 | import com.sixt.service.framework.rpc.LoadBalancer; 17 | import com.sixt.service.framework.servicetest.mockservice.ServiceImpersonatorLoadBalancer; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.io.IOException; 22 | import java.net.ServerSocket; 23 | 24 | public class ServiceImpersonatorModule extends TestInjectionModule { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(ServiceImpersonatorModule.class); 27 | 28 | private ServerSocket serverSocket; 29 | 30 | public ServiceImpersonatorModule(String serviceName, ServiceProperties props) { 31 | super(serviceName, props); 32 | serverSocket = buildServerSocket(); 33 | serviceProperties.initialize(new String[0]); 34 | serviceProperties.setServicePort(serverSocket.getLocalPort()); 35 | } 36 | 37 | @Override 38 | protected void configure() { 39 | bind(ServiceProperties.class).toInstance(serviceProperties); 40 | bind(ServerSocket.class).toInstance(serverSocket); 41 | bind(LoadBalancer.class).to(ServiceImpersonatorLoadBalancer.class); 42 | } 43 | 44 | private ServerSocket buildServerSocket() { 45 | return nextServerSocket(); 46 | } 47 | 48 | private ServerSocket nextServerSocket() { 49 | int port = portDictionary.newInternalPortForImpersonated(serviceProperties.getServiceName()); 50 | try { 51 | return new ServerSocket(port); 52 | } catch (IOException e) { 53 | logger.error("Could not create ServerSocket on port {}", port); 54 | return null; 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /sit/src/main/java/com/sixt/service/test_service/handler/CallsAnotherServiceHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.test_service.handler; 14 | 15 | import com.sixt.service.another_service.api.AnotherServiceOuterClass; 16 | import com.sixt.service.framework.OrangeContext; 17 | import com.sixt.service.framework.ServiceMethodHandler; 18 | import com.sixt.service.framework.annotation.RpcHandler; 19 | import com.sixt.service.framework.rpc.RpcCallException; 20 | import com.sixt.service.framework.rpc.RpcClient; 21 | import com.sixt.service.framework.rpc.RpcClientFactory; 22 | import com.sixt.service.test_service.api.TestServiceOuterClass; 23 | import com.sixt.service.test_service.api.TestServiceOuterClass.CallAnotherServiceCommand; 24 | import com.sixt.service.test_service.api.TestServiceOuterClass.CallAnotherServiceResponse; 25 | 26 | import javax.inject.Inject; 27 | import javax.inject.Singleton; 28 | 29 | @Singleton 30 | @RpcHandler("TestService.CallsAnotherService") 31 | public class CallsAnotherServiceHandler implements ServiceMethodHandler { 33 | 34 | private RpcClient rpcClient; 35 | 36 | @Inject 37 | public CallsAnotherServiceHandler(RpcClientFactory factory) { 38 | rpcClient = factory.newClient("com.sixt.service.another-service", 39 | "AnotherService.ImpersonatorTest", 40 | CallAnotherServiceResponse.class).build(); 41 | } 42 | 43 | @Override 44 | public CallAnotherServiceResponse handleRequest(CallAnotherServiceCommand command, 45 | OrangeContext ctx) throws RpcCallException { 46 | rpcClient.callSynchronous(CallAnotherServiceCommand.getDefaultInstance(), ctx); 47 | return CallAnotherServiceResponse.getDefaultInstance(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/jetty/RpcServletTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.jetty; 14 | 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | import org.mockito.Mock; 19 | import org.mockito.Mockito; 20 | import org.mockito.runners.MockitoJUnitRunner; 21 | 22 | import javax.servlet.http.HttpServletRequest; 23 | import javax.servlet.http.HttpServletResponse; 24 | 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.verifyNoMoreInteractions; 27 | 28 | @RunWith(MockitoJUnitRunner.class) 29 | public class RpcServletTest { 30 | 31 | private RpcServlet cut; 32 | 33 | @Mock 34 | private JsonHandler mockJsonHandler; 35 | 36 | @Mock 37 | private ProtobufHandler mockProtobufHandler; 38 | 39 | @Mock 40 | private HttpServletRequest mockHttpServletRequest; 41 | 42 | @Mock 43 | private HttpServletResponse mockHttpServletResponse; 44 | 45 | @Before 46 | public void setup() { 47 | cut = new RpcServlet(mockJsonHandler, mockProtobufHandler); 48 | } 49 | 50 | @Test 51 | public void doHead() throws Exception { 52 | // given 53 | 54 | // when 55 | cut.doHead(mockHttpServletRequest, mockHttpServletResponse); 56 | } 57 | 58 | @Test 59 | public void doGet() throws Exception { 60 | 61 | } 62 | 63 | @Test 64 | public void doPost_CannotServe_ServiceUnavailable() throws Exception { 65 | // given 66 | cut.canServeRequests.set(false); 67 | 68 | // when 69 | cut.doPost(mockHttpServletRequest, mockHttpServletResponse); 70 | 71 | // then 72 | verify(mockHttpServletResponse).setStatus(Mockito.eq(HttpServletResponse.SC_SERVICE_UNAVAILABLE)); 73 | verifyNoMoreInteractions(mockJsonHandler, mockProtobufHandler); 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/protobuf/ProtobufRpcRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.protobuf; 14 | 15 | import com.google.common.primitives.Ints; 16 | import com.google.protobuf.Message; 17 | 18 | /** 19 | * Micro framework implementation that encapsulates the protobuf 20 | * envelope and body for a request 21 | */ 22 | public class ProtobufRpcRequest { 23 | 24 | private String serviceMethod; 25 | private Long sequenceNumber; 26 | private Message payload; 27 | 28 | public ProtobufRpcRequest(String serviceMethod, Message payload) { 29 | this.serviceMethod = serviceMethod; 30 | this.payload = payload; 31 | } 32 | 33 | public Message getPayload() { 34 | return payload; 35 | } 36 | 37 | public byte[] getProtobufData() { 38 | byte[] envelopeData = getEnvelope().toByteArray(); 39 | byte[] payloadData = getPayload().toByteArray(); 40 | int size = envelopeData.length + payloadData.length + 8; 41 | byte[] retval = new byte[size]; 42 | int offset = 0; 43 | System.arraycopy(Ints.toByteArray(envelopeData.length), 0, retval, offset, 4); 44 | offset += 4; 45 | System.arraycopy(envelopeData, 0, retval, offset, envelopeData.length); 46 | offset += envelopeData.length; 47 | System.arraycopy(Ints.toByteArray(payloadData.length), 0, retval, offset, 4); 48 | offset += 4; 49 | System.arraycopy(payloadData, 0, retval, offset, payloadData.length); 50 | 51 | return retval; 52 | } 53 | 54 | private RpcEnvelope.Request getEnvelope() { 55 | RpcEnvelope.Request.Builder builder 56 | = RpcEnvelope.Request.newBuilder().setServiceMethod(serviceMethod); 57 | 58 | if (sequenceNumber != null) { 59 | builder.setSequenceNumber(sequenceNumber); 60 | } 61 | 62 | return builder.build(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/database/SchemaMigratorIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.database; 14 | 15 | import com.sixt.service.framework.ServiceProperties; 16 | import com.sixt.service.framework.health.HealthCheck; 17 | import org.flywaydb.core.Flyway; 18 | import org.flywaydb.core.api.FlywayException; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.mockito.Mockito.*; 24 | 25 | public class SchemaMigratorIntegrationTest { 26 | 27 | private ServiceProperties props; 28 | private SchemaMigrator migrator; 29 | private DatabaseMigrationContributor healthcheck; 30 | private ConnectionVerifier connectionVerifier; 31 | 32 | @Before 33 | public void setup() { 34 | props = new ServiceProperties(); 35 | healthcheck = new DatabaseMigrationContributor(props); 36 | connectionVerifier = mock(ConnectionVerifier.class); 37 | migrator = new SchemaMigrator(props, healthcheck, connectionVerifier, new Flyway()); 38 | } 39 | 40 | @Test(expected = IllegalArgumentException.class) 41 | public void sleepForDatabaseCreation() throws InterruptedException { 42 | doThrow(new IllegalArgumentException()).when(connectionVerifier).verifyDatabaseExists(); 43 | migrator.badConfigSemaphore.release(); 44 | migrator.migrate(); 45 | HealthCheck hc = healthcheck.getHealthCheck(); 46 | assertThat(hc.getStatus()).isEqualTo(HealthCheck.Status.FAIL); 47 | } 48 | 49 | @Test 50 | public void sleepAfterFailedMigration() { 51 | props.addProperty("databaseServer", "foo"); 52 | Flyway flyway = mock(Flyway.class); 53 | when(flyway.migrate()).thenThrow(new FlywayException()); 54 | migrator.flyway = flyway; 55 | migrator.flywayFailedSemaphore.release(); 56 | migrator.migrate(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/messaging/DelayAndRetryOnRecoverableErrors.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka.messaging; 14 | 15 | // Thread safety: single thread use 16 | public class DelayAndRetryOnRecoverableErrors implements FailedMessageProcessor { 17 | 18 | private final FailedMessageProcessor fallbackStrategy; 19 | private final RetryDelayer retryStrategy; 20 | 21 | Message lastFailedMessage = null; 22 | 23 | public DelayAndRetryOnRecoverableErrors(FailedMessageProcessor fallbackStrategy, RetryDelayer retryStrategy) { 24 | this.fallbackStrategy = fallbackStrategy; 25 | this.retryStrategy = retryStrategy; 26 | } 27 | 28 | @Override 29 | public boolean onFailedMessage(Message failed, Throwable failureCause) { 30 | if (!isRecoverable(failureCause)) { 31 | return fallbackStrategy.onFailedMessage(failed, failureCause); 32 | } 33 | 34 | // we have a new failure case 35 | if (failed != lastFailedMessage) { // reference equals by intention 36 | lastFailedMessage = failed; 37 | retryStrategy.reset(); 38 | } 39 | 40 | // blocks the message handler thread -> flow control may pause the partition 41 | boolean shouldRetry = retryStrategy.delay(); 42 | 43 | if (!shouldRetry) { 44 | return fallbackStrategy.onFailedMessage(failed, failureCause); 45 | } 46 | 47 | return shouldRetry; 48 | } 49 | 50 | /** 51 | * This method can be overridden to specify custom behaviour. 52 | *

53 | * The default implementation simply returns false (non-retryable) in all cases. 54 | * 55 | * @param failureCause The exception thrown when delivering the message. 56 | * @return true if the message delivery should be retried, false otherwise. 57 | */ 58 | protected boolean isRecoverable(Throwable failureCause) { 59 | return false; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/kafka/KafkaTopicInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.kafka; 14 | 15 | import org.apache.kafka.common.TopicPartition; 16 | 17 | public class KafkaTopicInfo { 18 | 19 | protected String topic; 20 | protected int partition; 21 | protected long offset; 22 | protected String key; 23 | 24 | public KafkaTopicInfo(String topic, int partition, long offset, String key) { 25 | this.topic = topic; 26 | this.partition = partition; 27 | this.offset = offset; 28 | this.key = key; 29 | } 30 | 31 | public String getTopic() { 32 | return topic; 33 | } 34 | 35 | public int getPartition() { 36 | return partition; 37 | } 38 | 39 | public long getOffset() { 40 | return offset; 41 | } 42 | 43 | public TopicPartition getTopicPartition() { 44 | return new TopicPartition(topic, partition); 45 | } 46 | 47 | public String getKey() { 48 | return key; 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | 56 | KafkaTopicInfo topicInfo = (KafkaTopicInfo) o; 57 | 58 | if (getPartition() != topicInfo.getPartition()) return false; 59 | if (getOffset() != topicInfo.getOffset()) return false; 60 | if (getTopic() != null ? !getTopic().equals(topicInfo.getTopic()) : topicInfo.getTopic() != null) return false; 61 | return getKey() != null ? getKey().equals(topicInfo.getKey()) : topicInfo.getKey() == null; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | int result = getTopic() != null ? getTopic().hashCode() : 0; 67 | result = 31 * result + getPartition(); 68 | result = 31 * result + (int) (getOffset() ^ (getOffset() >>> 32)); 69 | result = 31 * result + (getKey() != null ? getKey().hashCode() : 0); 70 | return result; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/protobuf/ProtobufRpcResponseTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.protobuf; 14 | 15 | import com.google.common.primitives.Ints; 16 | import com.sixt.service.framework.rpc.RpcCallException; 17 | import org.junit.Test; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.assertj.core.api.Assertions.catchThrowable; 21 | 22 | public class ProtobufRpcResponseTest { 23 | 24 | @Test 25 | public void testNegativeHeaderLength() throws Exception { 26 | byte[] data = Ints.toByteArray(-1); 27 | 28 | Throwable thrown = catchThrowable( () -> new ProtobufRpcResponse(data)); 29 | 30 | assertThat(thrown).isExactlyInstanceOf(RpcCallException.class); 31 | assertThat(thrown).hasFieldOrPropertyWithValue("category", RpcCallException.Category.InternalServerError); 32 | } 33 | 34 | @Test 35 | public void testTooBigHeaderLength() throws Exception { 36 | byte[] data = Ints.toByteArray(19_000_000); 37 | 38 | Throwable thrown = catchThrowable( () -> new ProtobufRpcResponse(data)); 39 | 40 | assertThat(thrown).isExactlyInstanceOf(RpcCallException.class); 41 | assertThat(thrown).hasFieldOrPropertyWithValue("category", RpcCallException.Category.InternalServerError); 42 | } 43 | 44 | @Test 45 | public void testEnsurePrintableShort() throws Exception { 46 | byte[] testData = new byte[65]; 47 | for (int i = 0; i < 65; i++) { 48 | testData[i] = (byte) i; 49 | } 50 | String result = ProtobufRpcResponse.ensurePrintable(testData, 70); 51 | assertThat(result).isEqualTo("[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, " + 52 | "0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, " + 53 | "0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, !, \", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, " + 54 | "1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @]"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/sixt/service/framework/rpc/ServiceEndpointListTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.rpc; 14 | 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | 21 | public class ServiceEndpointListTest { 22 | 23 | private ServiceEndpointList list = new ServiceEndpointList(); 24 | private ServiceDependencyHealthCheck dependencyHealthCheck; 25 | 26 | @Before 27 | public void testSetup() { 28 | dependencyHealthCheck = mock(ServiceDependencyHealthCheck.class); 29 | } 30 | 31 | @Test 32 | public void addNode() { 33 | ServiceEndpoint sep1 = new ServiceEndpoint(null, "1.1.1.1:80", "dc1", dependencyHealthCheck); 34 | ServiceEndpoint sep2 = new ServiceEndpoint(null, "1.1.1.2:80", "dc1", dependencyHealthCheck); 35 | ServiceEndpoint sep3 = new ServiceEndpoint(null, "1.1.1.3:80", "dc1", dependencyHealthCheck); 36 | list.add(sep1); 37 | list.add(sep2); 38 | list.add(sep3); 39 | assertThat(list.size()).isEqualTo(3); 40 | assertThat(list.nextAvailable()).isEqualTo(sep3); 41 | assertThat(list.nextAvailable()).isEqualTo(sep1); 42 | assertThat(list.nextAvailable()).isEqualTo(sep2); 43 | assertThat(list.nextAvailable()).isEqualTo(sep3); 44 | list.updateEndpointHealth(new ServiceEndpoint(null, "1.1.1.2:80", "dc1", dependencyHealthCheck), 45 | CircuitBreakerState.State.UNHEALTHY); 46 | list.updateEndpointHealth(new ServiceEndpoint(null, "11.11.11.12:80", "dc1", dependencyHealthCheck), 47 | CircuitBreakerState.State.UNHEALTHY); 48 | } 49 | 50 | @Test 51 | public void verifyToString() { 52 | ServiceEndpoint sep1 = new ServiceEndpoint(null, "1.1.1.1:80", "dc1", dependencyHealthCheck); 53 | ServiceEndpointNode node = new ServiceEndpointNode(sep1); 54 | assertThat(node.toString()).isEqualTo(sep1.toString()); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/sixt/service/framework/metrics/GoCounter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | * not use this file except in compliance with the License. You may obtain a 5 | * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | * License for the specific language governing permissions and limitations 10 | * under the License. 11 | */ 12 | 13 | package com.sixt.service.framework.metrics; 14 | 15 | import com.codahale.metrics.Counter; 16 | import com.codahale.metrics.Metric; 17 | 18 | public class GoCounter extends Counter implements Metric { 19 | 20 | //The structure of codahale metrics don't mesh with our go metrics. 21 | //To track failures and successes in different buckets requires using 22 | // multiple underlying Counter objects; 23 | protected Counter successCounter; 24 | protected Counter failureCounter; 25 | protected String name; 26 | 27 | public GoCounter(String name) { 28 | this.name = name; 29 | successCounter = new Counter(); 30 | failureCounter = new Counter(); 31 | } 32 | 33 | public void incSuccess() { 34 | successCounter.inc(); 35 | } 36 | 37 | public void incSuccess(long n) { 38 | successCounter.inc(n); 39 | } 40 | 41 | public void incFailure() { 42 | failureCounter.inc(); 43 | } 44 | 45 | public void incFailure(long n) { 46 | failureCounter.inc(n); 47 | } 48 | 49 | public void decSuccess() { 50 | successCounter.dec(); 51 | } 52 | 53 | public void decSuccess(long n) { 54 | successCounter.dec(n); 55 | } 56 | 57 | public void decFailure() { 58 | failureCounter.dec(); 59 | } 60 | 61 | public void decFailure(long n) { 62 | failureCounter.dec(n); 63 | } 64 | 65 | public Counter getSuccessCounter() { 66 | return successCounter; 67 | } 68 | 69 | public Counter getFailureCounter() { 70 | return failureCounter; 71 | } 72 | 73 | public long getSuccessCount() { 74 | return successCounter.getCount(); 75 | } 76 | 77 | public long getFailureCount() { 78 | return failureCounter.getCount(); 79 | } 80 | 81 | public String getName() { 82 | return name; 83 | } 84 | 85 | public void reset() { 86 | successCounter = new Counter(); 87 | failureCounter = new Counter(); 88 | } 89 | 90 | } 91 | --------------------------------------------------------------------------------