/** * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain a * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.sixt.service.framework.servicetest.mockservice; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.protobuf.Message; import com.sixt.service.framework.MethodHandlerDictionary; import com.sixt.service.framework.ServiceProperties; import com.sixt.service.framework.health.HealthCheckManager; import com.sixt.service.framework.injection.ServiceRegistryModule; import com.sixt.service.framework.injection.TracingModule; import com.sixt.service.framework.kafka.KafkaPublisher; import com.sixt.service.framework.kafka.KafkaPublisherFactory; import com.sixt.service.framework.protobuf.ProtobufUtil; import com.sixt.service.framework.registry.ServiceDiscoveryProvider; import com.sixt.service.framework.registry.consul.RegistrationManager; import com.sixt.service.framework.rpc.LoadBalancerFactory; import com.sixt.service.framework.rpc.RpcClientFactory; import com.sixt.service.framework.servicetest.injection.TestInjectionModule; import com.sixt.service.framework.util.Sleeper; import com.squareup.wire.schema.internal.parser.RpcMethodDefinition; import com.squareup.wire.schema.internal.parser.RpcMethodScanner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class ServiceImpersonator { private static final Logger logger = LoggerFactory.getLogger(ServiceImpersonator.class); public static final int SLEEP_AFTER_PUBLISH = 500; protected String serviceName; protected RegistrationManager registrationManager; protected MessageHandler messageHandler; protected MethodHandlerDictionary methodHandlers; protected ServiceProperties serviceProperties; protected HealthCheckManager healthCheckManager; protected Injector injector; protected RpcMethodScanner rpcMethodScanner; protected KafkaPublisherFactory factory; protected LoadBalancerFactory loadBalancerFactory; private Map<String, KafkaPublisher> topicToPublisher = new HashMap<>(); private int sleepAfterPublish = SLEEP_AFTER_PUBLISH; public ServiceImpersonator(String serviceName) throws Exception { //ServiceImpersonator needs its own injection stack so that each mock service //and service under servicetest get their own ecosystem this.serviceName = serviceName; TestInjectionModule testInjectionModule = new TestInjectionModule(serviceName); serviceProperties = testInjectionModule.getServiceProperties(); serviceProperties.setServiceName(serviceName); //has to be before getting regMgr serviceProperties.setServiceInstanceId(UUID.randomUUID().toString()); serviceProperties.addProperty("registry", "consul"); injector = Guice.createInjector(testInjectionModule, new ServiceRegistryModule(serviceProperties), new TracingModule(serviceProperties)); ServiceDiscoveryProvider provider = injector.getInstance(ServiceDiscoveryProvider.class); LoadBalancerFactory lbFactory = injector.getInstance(LoadBalancerFactory.class); lbFactory.initialize(provider); registrationManager = injector.getInstance(RegistrationManager.class); healthCheckManager = injector.getInstance(HealthCheckManager.class); rpcMethodScanner = new RpcMethodScanner(injector.getInstance(RpcClientFactory.class)); methodHandlers = injector.getInstance(MethodHandlerDictionary.class); messageHandler = injector.getInstance(MessageHandler.class); factory = injector.getInstance(KafkaPublisherFactory.class); initialize(); } public void setSleepAfterPublish(int sleepAfterPublish) { this.sleepAfterPublish = sleepAfterPublish; } private void initialize() throws Exception { buildMethodHandlers(serviceName.replaceAll("-", "_")); registrationManager.setRegisteredHandlers(methodHandlers.getMethodHandlers()); registrationManager.register(); messageHandler.setServiceName(serviceName); messageHandler.start(); while(! registrationManager.isRegistered()) { logger.info("Waiting for service registration of {}", serviceName); new Sleeper().sleepNoException(100); } healthCheckManager.initialize(); loadBalancerFactory = injector.getInstance(LoadBalancerFactory.class); loadBalancerFactory.getLoadBalancer(serviceName).waitForServiceInstance(); } public void shutdown() { healthCheckManager.shutdown(); loadBalancerFactory.shutdown(); registrationManager.shutdown(); messageHandler.shutdown(); } public ServiceImpersonator addMapping(CommandResponseMapping mapping) { ServiceMethodProxy proxy = ((ServiceMethodProxy) this.methodHandlers.getMethodHandler(mapping.getCommand())); if (proxy == null) { throw new RuntimeException("The method " + mapping.getCommand() + " for " + this.serviceName + " could not be found"); } logger.info("Adding mock response mapping for {}", mapping.getCommand()); proxy.setResponse(mapping.getResponse()); return this; } /** * Publish an event to kafka. We create one publisher per topic. Uses a null key. * * @param topic The topic to publish under * @param event The event to publish */ public void publishEvent(String topic, Message event) { publishEventWithKey(topic, null, event); } /** * Publish an event to kafka. We create one publisher per topic. * * @param topic The topic to publish under * @param key The key for the event * @param event The event to publish */ public void publishEventWithKey(String topic, String key, Message event) { KafkaPublisher publisher = topicToPublisher.get(topic); if (publisher == null) { publisher = factory.newBuilder(topic).build(); topicToPublisher.put(topic, publisher); } String jsonEvent = ProtobufUtil.protobufToJson(event).toString(); boolean isPublished = publisher.publishSyncWithKey(key, jsonEvent); if (isPublished){ logger.info("Published event: {}", jsonEvent); new Sleeper().sleepNoException(sleepAfterPublish); } else{ logger.warn("Publishing event message {} to Kafka topic {} failed", jsonEvent, topic); } } /** * Get counter how often a service method was called * * @param serviceMethodName The service method name e.g. Telematics.LockVehicle * @return Number of calls of a service method */ public int getServiceMethodCallCount(String serviceMethodName) throws Exception { ServiceMethodProxy serviceMethodProxy = (ServiceMethodProxy) methodHandlers.getMethodHandler(serviceMethodName); if(serviceMethodProxy == null){ throw new Exception("No method handler found for service method '" + serviceMethodName + "'"); } return serviceMethodProxy.getMethodCallCounter(); } /** * Reset counter how often a service method was called to 0 * * @param serviceMethodName The service method name e.g. Telematics.LockVehicle */ public void resetServiceMethodCallCount(String serviceMethodName) throws Exception { ServiceMethodProxy serviceMethodProxy = (ServiceMethodProxy) methodHandlers.getMethodHandler(serviceMethodName); if(serviceMethodProxy == null){ throw new Exception("No method handler found for service method '" + serviceMethodName + "'"); } serviceMethodProxy.resetMethodCallCounter(); } private void buildMethodHandlers(String serviceName) throws Exception { // get the generated proto classes List<String> protoClasses = rpcMethodScanner.getGeneratedProtoClasses(serviceName); List<RpcMethodDefinition> defs = rpcMethodScanner.getRpcMethodDefinitions(serviceName); for (RpcMethodDefinition def : defs) { ServiceMethodProxy handler = buildMethodHandler(def, protoClasses); methodHandlers.put(def.getMethodName(), handler); logger.info("Adding mock method handler for {}", def.getMethodName()); } } private ServiceMethodProxy buildMethodHandler(RpcMethodDefinition def, List<String> protoClasses) throws ClassNotFoundException { Class<?> requestClass = rpcMethodScanner.findProtobufClass(protoClasses, def.getRequestType()); Class<?> responseClass = rpcMethodScanner.findProtobufClass(protoClasses, def.getResponseType()); return new ServiceMethodProxy(requestClass, responseClass); } }