All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.appform.ranger.discovery.bundle.ServiceDiscoveryBundle Maven / Gradle / Ivy

There is a newer version: 1.1-RC4
Show newest version
/*
 * Copyright (c) 2019 Santanu Sinha 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.appform.ranger.discovery.bundle;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import io.appform.ranger.client.RangerClient;
import io.appform.ranger.client.zk.SimpleRangerZKClient;
import io.appform.ranger.common.server.ShardInfo;
import io.appform.ranger.core.finder.serviceregistry.MapBasedServiceRegistry;
import io.appform.ranger.core.healthcheck.Healthcheck;
import io.appform.ranger.core.healthcheck.HealthcheckStatus;
import io.appform.ranger.core.healthservice.TimeEntity;
import io.appform.ranger.core.healthservice.monitor.IsolatedHealthMonitor;
import io.appform.ranger.core.model.ServiceNode;
import io.appform.ranger.core.model.ShardSelector;
import io.appform.ranger.core.serviceprovider.ServiceProvider;
import io.appform.ranger.discovery.bundle.healthchecks.InitialDelayChecker;
import io.appform.ranger.discovery.bundle.healthchecks.InternalHealthChecker;
import io.appform.ranger.discovery.bundle.healthchecks.RotationCheck;
import io.appform.ranger.discovery.bundle.id.IdGenerator;
import io.appform.ranger.discovery.bundle.id.NodeIdManager;
import io.appform.ranger.discovery.bundle.id.constraints.IdValidationConstraint;
import io.appform.ranger.discovery.bundle.monitors.DropwizardHealthMonitor;
import io.appform.ranger.discovery.bundle.monitors.DropwizardServerStartupCheck;
import io.appform.ranger.discovery.bundle.resolvers.DefaultNodeInfoResolver;
import io.appform.ranger.discovery.bundle.resolvers.DefaultPortSchemeResolver;
import io.appform.ranger.discovery.bundle.resolvers.NodeInfoResolver;
import io.appform.ranger.discovery.bundle.resolvers.PortSchemeResolver;
import io.appform.ranger.discovery.bundle.rotationstatus.BIRTask;
import io.appform.ranger.discovery.bundle.rotationstatus.DropwizardServerStatus;
import io.appform.ranger.discovery.bundle.rotationstatus.OORTask;
import io.appform.ranger.discovery.bundle.rotationstatus.RotationStatus;
import io.appform.ranger.discovery.bundle.selectors.HierarchicalEnvironmentAwareShardSelector;
import io.appform.ranger.discovery.bundle.util.ConfigurationUtils;
import io.appform.ranger.zookeeper.ServiceProviderBuilders;
import io.appform.ranger.zookeeper.serde.ZkNodeDataSerializer;
import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryForever;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static io.appform.ranger.discovery.bundle.Constants.LOCAL_ADDRESSES;


/**
 * A dropwizard bundle for service discovery.
 */
@SuppressWarnings("unused")
@Slf4j
public abstract class ServiceDiscoveryBundle implements ConfiguredBundle {

    private final List healthchecks = Lists.newArrayList();
    private final List globalIdConstraints;
    private ServiceDiscoveryConfiguration serviceDiscoveryConfiguration;
    private ServiceProvider> serviceProvider;

    @Getter
    private CuratorFramework curator;
    @Getter
    private RangerClient> serviceDiscoveryClient;
    @Getter
    @VisibleForTesting
    private RotationStatus rotationStatus;
    @Getter
    @VisibleForTesting
    private DropwizardServerStatus serverStatus;

    protected ServiceDiscoveryBundle() {
        globalIdConstraints = Collections.emptyList();
    }

    protected ServiceDiscoveryBundle(List globalIdConstraints) {
        this.globalIdConstraints = globalIdConstraints != null
                                   ? globalIdConstraints
                                   : Collections.emptyList();
    }

    @Override
    public void initialize(Bootstrap bootstrap) {

    }

    @Override
    public void run(T configuration,
                    Environment environment) throws Exception {
        val portSchemeResolver = createPortSchemeResolver();
        Preconditions.checkNotNull(portSchemeResolver, "Port scheme resolver can't be null");
        val portScheme = portSchemeResolver.resolve(configuration);
        serviceDiscoveryConfiguration = getRangerConfiguration(configuration);
        val objectMapper = environment.getObjectMapper();
        val namespace = serviceDiscoveryConfiguration.getNamespace();
        val serviceName = getServiceName(configuration);
        val hostname = getHost();
        val port = getPort(configuration);
        val initialCriteria = getInitialCriteria(configuration);
        val useInitialCriteria = alwaysMergeWithInitialCriteria(configuration);
        val shardSelector = getShardSelector(configuration);
        rotationStatus = new RotationStatus(serviceDiscoveryConfiguration.isInitialRotationStatus());
        serverStatus = new DropwizardServerStatus(false);
        curator = CuratorFrameworkFactory.builder()
                .connectString(serviceDiscoveryConfiguration.getZookeeper())
                .namespace(namespace)
                .retryPolicy(new RetryForever(serviceDiscoveryConfiguration.getConnectionRetryIntervalMillis()))
                .build();
        serviceProvider = buildServiceProvider(environment, objectMapper, namespace, serviceName, hostname, port,
                portScheme);
        serviceDiscoveryClient = buildDiscoveryClient(environment, namespace, serviceName, initialCriteria,
                useInitialCriteria, shardSelector);
        environment.lifecycle()
                .manage(new ServiceDiscoveryManager(serviceName));
        environment.jersey()
                .register(new InfoResource(serviceDiscoveryClient));
        environment.admin()
                .addTask(new OORTask(rotationStatus));
        environment.admin()
                .addTask(new BIRTask(rotationStatus));
    }

    protected ShardSelector> getShardSelector(T configuration) {
        return new HierarchicalEnvironmentAwareShardSelector(getRangerConfiguration(configuration).getEnvironment());
    }

    protected abstract ServiceDiscoveryConfiguration getRangerConfiguration(T configuration);

    protected abstract String getServiceName(T configuration);

    protected NodeInfoResolver createNodeInfoResolver() {
        return new DefaultNodeInfoResolver();
    }

    protected PortSchemeResolver createPortSchemeResolver() {
        return new DefaultPortSchemeResolver<>();
    }

    /**
     * Override the following if you require.
     **/
    protected Predicate getInitialCriteria(T configuration) {
        return shardInfo -> true;
    }

    protected boolean alwaysMergeWithInitialCriteria(T configuration) {
        return false;
    }

    protected List> getHealthMonitors() {
        return Collections.emptyList();
    }

    @SuppressWarnings("unused")
    protected int getPort(T configuration) {
        Preconditions.checkArgument(Constants.DEFAULT_PORT != serviceDiscoveryConfiguration.getPublishedPort()
                        && 0 != serviceDiscoveryConfiguration.getPublishedPort(),
                "Looks like publishedPost has not been set and getPort() has not been overridden. This is wrong. \n"
                        + "Either set publishedPort in config or override getPort() to return the port on which the service is running");
        return serviceDiscoveryConfiguration.getPublishedPort();
    }

    protected String getHost() throws UnknownHostException {
        val host = ConfigurationUtils.resolveNonEmptyPublishedHost(serviceDiscoveryConfiguration.getPublishedHost());

        val publishedHostAddress = InetAddress.getByName(host)
                .getHostAddress();

        val zkHostAddresses = ConfigurationUtils.resolveZookeeperHosts(serviceDiscoveryConfiguration.getZookeeper())
                .stream()
                .map(zkHost -> {
                    try {
                        return InetAddress.getByName(zkHost)
                                .getHostAddress();
                    } catch (UnknownHostException e) {
                        throw new IllegalArgumentException(
                                String.format("Couldn't resolve host address for zkHost : %s", zkHost), e);
                    }
                })
                .collect(Collectors.toSet());

        Preconditions.checkArgument(
                !LOCAL_ADDRESSES.contains(publishedHostAddress) || LOCAL_ADDRESSES.containsAll(zkHostAddresses),
                "Not allowed to publish localhost address to remote zookeeper");

        return host;
    }

    public void registerHealthcheck(Healthcheck healthcheck) {
        this.healthchecks.add(healthcheck);
    }

    public void registerHealthchecks(List healthchecks) {
        this.healthchecks.addAll(healthchecks);
    }


    private RangerClient> buildDiscoveryClient(Environment environment,
                                                                                             String namespace,
                                                                                             String serviceName,
                                                                                             Predicate initialCriteria,
                                                                                             boolean mergeWithInitialCriteria,
                                                                                             ShardSelector> shardSelector) {
        return SimpleRangerZKClient.builder()
                .curatorFramework(curator)
                .namespace(namespace)
                .serviceName(serviceName)
                .mapper(environment.getObjectMapper())
                .nodeRefreshIntervalMs(serviceDiscoveryConfiguration.getRefreshTimeMs())
                .disableWatchers(serviceDiscoveryConfiguration.isDisableWatchers())
                .deserializer(data -> {
                    try {
                        return environment.getObjectMapper()
                                .readValue(data, new TypeReference>() {
                                });
                    } catch (IOException e) {
                        log.warn("Error parsing node data with value {}", new String(data));
                    }
                    return null;
                })
                .initialCriteria(initialCriteria)
                .alwaysUseInitialCriteria(mergeWithInitialCriteria)
                .shardSelector(shardSelector)
                .build();
    }

    private ServiceProvider> buildServiceProvider(Environment environment,
                                                                                             ObjectMapper objectMapper,
                                                                                             String namespace,
                                                                                             String serviceName,
                                                                                             String hostname,
                                                                                             int port,
                                                                                             String portScheme) {
        val nodeInfoResolver = createNodeInfoResolver();
        val nodeInfo = nodeInfoResolver.resolve(serviceDiscoveryConfiguration);
        val initialDelayForMonitor = serviceDiscoveryConfiguration.getInitialDelaySeconds() > 1
                                     ? serviceDiscoveryConfiguration.getInitialDelaySeconds() - 1
                                     : 0;
        val dwMonitoringInterval = serviceDiscoveryConfiguration.getDropwizardCheckInterval() == 0
                                   ? Constants.DEFAULT_DW_CHECK_INTERVAL
                                   : serviceDiscoveryConfiguration.getDropwizardCheckInterval();
        val dwMonitoringStaleness = Math.max(serviceDiscoveryConfiguration.getDropwizardCheckStaleness(),
                dwMonitoringInterval + 1);
        val serviceProviderBuilder = ServiceProviderBuilders.shardedServiceProviderBuilder()
                .withCuratorFramework(curator)
                .withNamespace(namespace)
                .withServiceName(serviceName)
                .withSerializer(data -> {
                    try {
                        return objectMapper.writeValueAsBytes(data);
                    } catch (Exception e) {
                        log.warn("Could not parse node data", e);
                    }
                    return null;
                })
                .withPortScheme(portScheme)
                .withNodeData(nodeInfo)
                .withHostname(hostname)
                .withPort(port)
                .withHealthcheck(new InternalHealthChecker(healthchecks))
                .withHealthcheck(new RotationCheck(rotationStatus))
                .withHealthcheck(new InitialDelayChecker(serviceDiscoveryConfiguration.getInitialDelaySeconds()))
                .withHealthcheck(new DropwizardServerStartupCheck(environment, serverStatus))
                .withIsolatedHealthMonitor(new DropwizardHealthMonitor(
                        new TimeEntity(initialDelayForMonitor, dwMonitoringInterval, TimeUnit.SECONDS),
                        dwMonitoringStaleness * 1_000L, environment))
                .withHealthUpdateIntervalMs(serviceDiscoveryConfiguration.getRefreshTimeMs())
                .withStaleUpdateThresholdMs(10000);

        val healthMonitors = getHealthMonitors();
        if (healthMonitors != null && !healthMonitors.isEmpty()) {
            healthMonitors.forEach(serviceProviderBuilder::withIsolatedHealthMonitor);
        }
        return serviceProviderBuilder.build();
    }

    @AllArgsConstructor
    private class ServiceDiscoveryManager implements Managed {

        private final String serviceName;

        @Override
        public void start() {
            log.debug("Starting the discovery manager");
            curator.start();
            serviceProvider.start();
            serviceDiscoveryClient.start();
            val nodeIdManager = new NodeIdManager(curator, serviceName);
            IdGenerator.initialize(nodeIdManager.fixNodeId(), globalIdConstraints, Collections.emptyMap());
            log.debug("Discovery manager has been successfully started.");
        }

        @Override
        public void stop() {
            serviceDiscoveryClient.stop();
            serviceProvider.stop();
            curator.close();
            IdGenerator.cleanUp();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy