Java tutorial
/* Copyright 2015 Alfio Zappala Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package co.runrightfast.vertx.core.verticles.verticleManager; import co.runrightfast.core.application.event.AppEvent; import co.runrightfast.core.application.event.AppEventLogger; import static co.runrightfast.core.application.event.ApplicationEvents.VERTICLE_DEPLOYMENT_FAILED; import static co.runrightfast.core.application.event.ApplicationEvents.VERTICLE_DEPLOYMENT_SUCCESS; import co.runrightfast.core.application.services.healthchecks.HealthCheckConfig; import co.runrightfast.core.application.services.healthchecks.RunRightFastHealthCheck; import co.runrightfast.vertx.core.RunRightFastVerticle; import co.runrightfast.vertx.core.RunRightFastVerticleId; import static co.runrightfast.vertx.core.RunRightFastVerticleId.RUNRIGHTFAST_GROUP; import co.runrightfast.vertx.core.eventbus.EventBusAddressMessageMapping; import co.runrightfast.vertx.core.eventbus.MessageConsumerConfig; import co.runrightfast.vertx.core.protobuf.MessageConversions; import static co.runrightfast.vertx.core.protobuf.MessageConversions.toVerticleId; import co.runrightfast.vertx.core.verticles.messages.VerticleId; import co.runrightfast.vertx.core.verticles.verticleManager.messages.GetVerticleDeployments; import co.runrightfast.vertx.core.verticles.verticleManager.messages.HealthCheckResult; import co.runrightfast.vertx.core.verticles.verticleManager.messages.RunVerticleHealthChecks; import com.codahale.metrics.JmxReporter; import com.codahale.metrics.health.HealthCheck; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.vertx.core.DeploymentOptions; import io.vertx.core.eventbus.Message; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import javax.inject.Inject; import lombok.Getter; import lombok.NonNull; import lombok.extern.java.Log; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; /** * The verticle manager can be used by other verticles to manage a hierarchy of verticles. * * @author alfio */ @Log public final class RunRightFastVerticleManager extends RunRightFastVerticle { public static final RunRightFastVerticleId VERTICLE_ID = RunRightFastVerticleId.builder() .group(RUNRIGHTFAST_GROUP).name("verticle-manager").version("0.1.0").build(); @Getter private final RunRightFastVerticleId runRightFastVerticleId = VERTICLE_ID; @Getter private final Set<RunRightFastVerticleDeployment> deployments; /** * Maps the Vertx verticle deployment id to the RunRightFastVerticleDeployment */ @Getter private ImmutableMap<String, RunRightFastVerticleDeployment> deployedVerticles = ImmutableMap.of(); private ImmutableMap<RunRightFastVerticleDeployment, JmxReporter> verticleJmxReporters = ImmutableMap.of(); /** * These are all of the verticles that have been deployed JVM wide. */ private static final Map<String, RunRightFastVerticleDeployment> globalDeployedVerticles = new ConcurrentHashMap<>(); private static JmxReporter jmxReporterForSelf; private static final Lock lock = new ReentrantLock(); @Inject public RunRightFastVerticleManager(final AppEventLogger appEventLogger, final Set<RunRightFastVerticleDeployment> deployments) { super(appEventLogger); checkArgument(CollectionUtils.isNotEmpty(deployments)); this.deployments = ImmutableSet.copyOf(deployments); } @Override protected void startUp() { deployments.stream().forEach(this::deployVerticle); registerGetVerticleDeploymentsMessageConsumer(); registerRunVerticleHealthChecksMessageConsumer(); startJmxReporterForSelf(); } @Override protected void shutDown() { // by the time this verticle manager instance is being shutdown, all of its deployed instances would have been been shutdown, i.e., un-deployed deployedVerticles.keySet().forEach(globalDeployedVerticles::remove); verticleJmxReporters.values().forEach(reporter -> { reporter.stop(); reporter.close(); }); stopJmxReporterForSelf(); } private void registerRunVerticleHealthChecksMessageConsumer() { registerMessageConsumer( MessageConsumerConfig.<RunVerticleHealthChecks.Request, RunVerticleHealthChecks.Response>builder() .addressMessageMapping(EventBusAddressMessageMapping.builder() .address(eventBusAddress("run-verticle-healthchecks")) .requestDefaultInstance(RunVerticleHealthChecks.Request.getDefaultInstance()) .responseDefaultInstance(RunVerticleHealthChecks.Response.getDefaultInstance()) .build()) .handler(this::handleRunVerticleHealthChecksMessage).build()); } private void handleRunVerticleHealthChecksMessage( @NonNull final Message<RunVerticleHealthChecks.Request> message) { final RunVerticleHealthChecks.Response.Builder response = RunVerticleHealthChecks.Response.newBuilder(); final RunVerticleHealthChecks.Request request = message.body(); if (hasFilters(request)) { deployments.stream().filter(deployment -> { final RunRightFastVerticleId id = deployment.getRunRightFastVerticleId(); if (CollectionUtils.isEmpty(deployment.getHealthChecks())) { return false; } return request.getGroupsList().stream().filter(group -> group.equals(id.getGroup())).findFirst() .isPresent() || request.getNamesList().stream().filter(name -> name.equals(id.getName())).findFirst() .isPresent() || request.getVerticleIdsList().stream().filter(id::equalsVerticleId).findFirst() .isPresent(); }).map(this::runVerticleHealthChecks).forEach(response::addAllResults); } else { deployments.stream().map(this::runVerticleHealthChecks).forEach(response::addAllResults); } reply(message, response.build()); } private List<HealthCheckResult> runVerticleHealthChecks(final RunRightFastVerticleDeployment deployment) { final VerticleId verticleId = toVerticleId(deployment.getRunRightFastVerticleId()); final Set<RunRightFastHealthCheck> healthChecks = deployment.getHealthChecks(); return healthChecks.stream().map(healthCheck -> { final HealthCheckResult.Builder result = HealthCheckResult.newBuilder(); final HealthCheckConfig config = healthCheck.getConfig(); HealthCheck.Result healthCheckResult; try { healthCheckResult = healthCheck.getHealthCheck().execute(); } catch (final Exception e) { healthCheckResult = HealthCheck.Result.unhealthy(e); } result.setHealthCheckName(config.getName()); result.setHealthy(healthCheckResult.isHealthy()); if (StringUtils.isNotBlank(healthCheckResult.getMessage())) { result.setMessage(healthCheckResult.getMessage()); } if (healthCheckResult.getError() != null) { result.setExceptionStacktrace(ExceptionUtils.getStackTrace(healthCheckResult.getError())); } result.setVerticleId(verticleId); return result.build(); }).collect(Collectors.toList()); } private void registerGetVerticleDeploymentsMessageConsumer() { registerMessageConsumer( MessageConsumerConfig.<GetVerticleDeployments.Request, GetVerticleDeployments.Response>builder() .addressMessageMapping(EventBusAddressMessageMapping.builder() .address(eventBusAddress("get-verticle-deployments")) .requestDefaultInstance(GetVerticleDeployments.Request.getDefaultInstance()) .responseDefaultInstance(GetVerticleDeployments.Response.getDefaultInstance()) .build()) .handler(this::handleGetVerticleDeploymentsMessage).build()); } private void handleGetVerticleDeploymentsMessage( @NonNull final Message<GetVerticleDeployments.Request> message) { final GetVerticleDeployments.Response.Builder response = GetVerticleDeployments.Response.newBuilder(); final GetVerticleDeployments.Request request = message.body(); if (hasFilters(request)) { deployments.stream().filter(deployment -> { final RunRightFastVerticleId id = deployment.getRunRightFastVerticleId(); return request.getGroupsList().stream().filter(group -> group.equals(id.getGroup())).findFirst() .isPresent() || request.getNamesList().stream().filter(name -> name.equals(id.getName())).findFirst() .isPresent() || request.getVerticleIdsList().stream().filter(id::equalsVerticleId).findFirst() .isPresent(); }).map(deployment -> MessageConversions.toVerticleDeployment(deployment, getDeploymentIds(deployment))) .forEach(response::addDeployments); } else { deployments.stream().map( deployment -> MessageConversions.toVerticleDeployment(deployment, getDeploymentIds(deployment))) .forEach(response::addDeployments); } reply(message, response.build()); } private Set<String> getDeploymentIds(final RunRightFastVerticleDeployment deployment) { return deployedVerticles.entrySet().stream().filter(entry -> entry.getValue().equals(deployment)) .map(entry -> entry.getKey()).collect(Collectors.toSet()); } private boolean hasFilters(@NonNull final GetVerticleDeployments.Request request) { return request.getVerticleIdsCount() > 0 || request.getGroupsCount() > 0 || request.getNamesCount() > 0; } private boolean hasFilters(@NonNull final RunVerticleHealthChecks.Request request) { return request.getVerticleIdsCount() > 0 || request.getGroupsCount() > 0 || request.getNamesCount() > 0; } /** * The verticle is deployed asynchronously * * @param deployment config */ private void deployVerticle(@NonNull final RunRightFastVerticleDeployment deployment) { if (deployment.getDeploymentOptions().getInstances() == 1) { deployVerticleInstance(deployment, deployment.getVerticleInstance(), deployment.getDeploymentOptions()); } else { // when more than 1 instance needs to be deployed, we need to manage that because we want to ensure each verticle instance is // indeed a new instance and there is no shared state. The reason we need to do this is because we create the verticle instance and not Vertx. final DeploymentOptions deploymentOptions = new DeploymentOptions(deployment.getDeploymentOptions()) .setInstances(1); for (int i = 0; i < deployment.getDeploymentOptions().getInstances(); i++) { if (i == 0) { // resuse the original instance deployVerticleInstance(deployment, deployment.getVerticleInstance(), deploymentOptions); } else { // this creates a new instance of the verticle final RunRightFastVerticleDeployment deploymentInstance = deployment.withNewVerticleInstance(); deployVerticleInstance(deploymentInstance, deploymentInstance.getVerticleInstance(), deploymentOptions); } } } } private void deployVerticleInstance(@NonNull final RunRightFastVerticleDeployment deployment, @NonNull final RunRightFastVerticle verticle, final DeploymentOptions deploymentOptions) { verticle.setParentVerticleInstanceId(Optional.of(this.verticleInstanceId)); vertx.deployVerticle(verticle, deploymentOptions, result -> { if (result.succeeded()) { this.deployedVerticles = ImmutableMap.<String, RunRightFastVerticleDeployment>builder() .putAll(deployedVerticles).put(result.result(), deployment).build(); registerJmxReporter(deployment); appEventLogger.accept(AppEvent.info(VERTICLE_DEPLOYMENT_SUCCESS).setVerticleId(VERTICLE_ID) .setData(deployment).build()); globalDeployedVerticles.put(result.result(), deployment); } else { appEventLogger.accept(AppEvent.error(VERTICLE_DEPLOYMENT_FAILED).setVerticleId(VERTICLE_ID) .setData(deployment).setException(result.cause()).build()); } }); } private void registerJmxReporter(@NonNull final RunRightFastVerticleDeployment deployment) { if (verticleJmxReporters.containsKey(deployment)) { return; } if (globalDeployedVerticles.values().contains(deployment)) { return; } final RunRightFastVerticleId verticleId = deployment.getRunRightFastVerticleId(); final JmxReporter jmxReporter = JmxReporter.forRegistry(deployment.getMetricRegistry()) .inDomain(verticleId.verticleJmxDomain("metrics")).convertDurationsTo(TimeUnit.MILLISECONDS) .convertRatesTo(TimeUnit.SECONDS).build(); jmxReporter.start(); verticleJmxReporters = ImmutableMap.<RunRightFastVerticleDeployment, JmxReporter>builder() .putAll(verticleJmxReporters).put(deployment, jmxReporter).build(); } private void startJmxReporterForSelf() { if (jmxReporterForSelf != null) { return; } jmxReporterForSelf = JmxReporter.forRegistry(this.metricRegistry) .inDomain(runRightFastVerticleId.verticleJmxDomain("metrics")) .convertDurationsTo(TimeUnit.MILLISECONDS).convertRatesTo(TimeUnit.SECONDS).build(); jmxReporterForSelf.start(); } private void stopJmxReporterForSelf() { if (lock.tryLock()) { try { if (jmxReporterForSelf == null) { return; } jmxReporterForSelf.stop(); jmxReporterForSelf.close(); jmxReporterForSelf = null; } finally { lock.unlock(); } } } @Override public Set<RunRightFastHealthCheck> getHealthChecks() { return ImmutableSet.of(); } }