org.apache.usergrid.persistence.actorsystem.ActorSystemManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.usergrid.persistence.actorsystem.ActorSystemManagerImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.usergrid.persistence.actorsystem;

import akka.actor.*;
import akka.cluster.Cluster;
import akka.cluster.client.ClusterClient;
import akka.cluster.client.ClusterClientReceptionist;
import akka.cluster.client.ClusterClientSettings;
import akka.cluster.pubsub.DistributedPubSub;
import akka.cluster.pubsub.DistributedPubSubMediator;
import akka.pattern.Patterns;
import akka.util.Timeout;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.concurrent.Await;
import scala.concurrent.Future;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Singleton
public class ActorSystemManagerImpl implements ActorSystemManager {
    private static final Logger logger = LoggerFactory.getLogger(ActorSystemManagerImpl.class);

    private boolean started = false;

    private String hostname;
    private Integer port;
    private String currentRegion;

    private final ActorSystemFig actorSystemFig;
    private final List<RouterProducer> routerProducers = new ArrayList<>();
    private final Map<Class, String> routersByMessageType = new HashMap<>();
    private final Map<String, ActorRef> clusterClientsByRegion = new HashMap<String, ActorRef>(20);

    private ActorRef mediator;

    private ActorRef clientActor;

    private ListMultimap<String, String> seedsByRegion;

    private ActorSystem clusterSystem = null;

    @Inject
    public ActorSystemManagerImpl(ActorSystemFig actorSystemFig) {
        this.actorSystemFig = actorSystemFig;
    }

    public Set<String> getRegions() {
        return getSeedsByRegion().keySet();
    }

    /**
     * Init Akka ActorSystems and wait for request actors to start.
     */
    @Override
    public void start() {

        if (!StringUtils.isEmpty(actorSystemFig.getHostname())) {
            this.hostname = actorSystemFig.getHostname();
        } else {
            try {
                this.hostname = InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e) {
                logger.error("Cannot get hostname, defaulting to 'localhost': " + e.getMessage());
            }
        }

        this.currentRegion = actorSystemFig.getRegionLocal();
        this.port = null;

        initAkka();
        waitForClientActor();
    }

    /**
     * For testing purposes only; does not wait for request actors to start.
     */
    @Override
    public void start(String hostname, Integer port, String currentRegion) {

        this.hostname = hostname;
        this.currentRegion = currentRegion;
        this.port = port;

        initAkka();
    }

    @Override
    public boolean isReady() {
        return started;
    }

    @Override
    public void registerRouterProducer(RouterProducer routerProducer) {
        routerProducers.add(routerProducer);
    }

    @Override
    public ActorRef getClientActor() {
        return clientActor;
    }

    @Override
    public ActorRef getClusterClient(String region) {
        return clusterClientsByRegion.get(region);
    }

    @Override
    public String getCurrentRegion() {
        return currentRegion;
    }

    @Override
    public void publishToAllRegions(String topic, Object message, ActorRef sender) {

        // send to local subscribers to topic
        mediator.tell(new DistributedPubSubMediator.Publish(topic, message), sender);

        // send to each ClusterClient
        for (ActorRef clusterClient : clusterClientsByRegion.values()) {
            clusterClient.tell(new ClusterClient.Publish(topic, message), sender);
        }

    }

    private String getClusterName() {
        // better to not change this so rolling restarts of usergrid are unaffected
        return "ClusterSystem";
    }

    private void initAkka() {
        logger.info("Initializing Akka");

        // Create one actor system with request actor for each region

        if (StringUtils.isEmpty(currentRegion)) {
            throw new RuntimeException("No value specified for: " + ActorSystemFig.CLUSTER_REGIONS_LOCAL);
        }

        if (StringUtils.isEmpty(actorSystemFig.getRegionsList())) {
            throw new RuntimeException("No value specified for: " + ActorSystemFig.CLUSTER_REGIONS_LIST);
        }

        if (StringUtils.isEmpty(actorSystemFig.getSeeds())) {
            throw new RuntimeException("No value specified for: " + ActorSystemFig.CLUSTER_SEEDS);
        }

        List regionList = Arrays.asList(actorSystemFig.getRegionsList().toLowerCase().split(","));

        logger.info("Initializing Akka for hostname {} region {} regionList {} seeds {}", hostname, currentRegion,
                regionList, actorSystemFig.getSeeds());

        Config config = createConfiguration();

        clusterSystem = createClusterSystem(config);

        // register our cluster listener
        clusterSystem.actorOf(Props.create(ClusterListener.class, getSeedsByRegion(), getCurrentRegion()),
                "clusterListener");

        createClientActors(clusterSystem);

        mediator = DistributedPubSub.get(clusterSystem).mediator();
    }

    /**
     * Read Usergrid's list of seeds, put them in handy multi-map.
     */
    private ListMultimap<String, String> getSeedsByRegion() {

        if (seedsByRegion == null) {

            seedsByRegion = ArrayListMultimap.create();

            String[] regionSeeds = actorSystemFig.getSeeds().split(",");

            logger.info("Found region [{}] seeds [{}]", regionSeeds.length, regionSeeds);

            try {

                if (port != null) {

                    // we are testing, create seeds-by-region map for one region, one seed

                    String seed = "akka.tcp://" + getClusterName() + "@" + hostname + ":" + port;
                    seedsByRegion.put(currentRegion, seed);
                    logger.info("Akka testing, only starting one seed");

                } else { // create seeds-by-region map

                    for (String regionSeed : regionSeeds) {

                        String[] parts = regionSeed.split(":");
                        String region = parts[0];
                        String hostname = parts[1];

                        String regionPortString = parts.length > 2 ? parts[2] : actorSystemFig.getPort();

                        // all seeds in same region must use same port
                        // we assume 0th seed has the right port
                        final Integer regionPort;

                        if (port == null) {
                            regionPort = Integer.parseInt(regionPortString);
                        } else {
                            regionPort = port; // unless we are testing
                        }

                        String seed = "akka.tcp://" + getClusterName() + "@" + hostname + ":" + regionPort;

                        logger.info("Adding seed [{}] for region [{}]", seed, region);

                        seedsByRegion.put(region, seed);
                    }

                    if (seedsByRegion.keySet().isEmpty()) {
                        throw new RuntimeException(
                                "No seeds listed in 'parsing collection.akka.region.seeds' property.");
                    }
                }

            } catch (Exception e) {
                throw new RuntimeException("Error 'parsing collection.akka.region.seeds' property", e);
            }
        }

        return seedsByRegion;
    }

    /**
     * Read cluster config and add seed nodes to it.
     */
    private Config createConfiguration() {

        Config config = null;

        try {

            int numInstancesPerNode = 300; // expect this to be overridden by RouterProducers

            String region = currentRegion;

            List<String> seeds = getSeedsByRegion().get(region);

            logger.info("Akka Config for region [{}] is:\n" + "   Hostname [{}]\n" + "   Seeds [{}]\n", region,
                    hostname, seeds);

            int lastColon = seeds.get(0).lastIndexOf(":") + 1;
            final Integer regionPort = Integer.parseInt(seeds.get(0).substring(lastColon));

            Map<String, Object> configMap = new HashMap<String, Object>() {
                {

                    put("akka", new HashMap<String, Object>() {
                        {

                            put("blocking-io-dispatcher", new HashMap<String, Object>() {
                                {
                                    put("type", "Dispatcher");
                                    put("executor", actorSystemFig.getClusterIoExecutorType());
                                    put(actorSystemFig.getClusterIoExecutorType(), new HashMap<String, Object>() {
                                        {
                                            put("fixed-pool-size",
                                                    actorSystemFig.getClusterIoExecutorThreadPoolSize());
                                            put("rejection-policy",
                                                    actorSystemFig.getClusterIoExecutorRejectionPolicy());
                                        }
                                    });
                                }
                            });

                            put("remote", new HashMap<String, Object>() {
                                {
                                    put("netty.tcp", new HashMap<String, Object>() {
                                        {
                                            put("hostname", hostname);
                                            put("bind-hostname", hostname);
                                            put("port", regionPort);
                                        }
                                    });
                                }
                            });

                            put("cluster", new HashMap<String, Object>() {
                                {
                                    put("max-nr-of-instances-per-node", numInstancesPerNode); // this sets default if router does not set
                                    put("roles", Collections.singletonList("io"));
                                    put("seed-nodes", new ArrayList<String>() {
                                        {
                                            for (String seed : seeds) {
                                                add(seed);
                                            }
                                        }
                                    });
                                    put("failure-detector", new HashMap<String, Object>() {
                                        {
                                            put("threshold", "20");
                                            put("acceptable-heartbeat-pause", "6 s");
                                            put("heartbeat-interval", "1 s");
                                            put("heartbeat-request", new HashMap<String, Object>() {
                                                {
                                                    put("expected-response-after", "3 s");
                                                }
                                            });
                                        }
                                    });
                                }
                            });

                        }
                    });
                }
            };

            for (RouterProducer routerProducer : routerProducers) {
                routerProducer.addConfiguration(configMap);
            }

            config = ConfigFactory.parseMap(configMap).withFallback(ConfigFactory.load("application.conf"));

        } catch (Exception e) {
            throw new RuntimeException("Error reading and adding to cluster config", e);
        }

        return config;
    }

    /**
     * Create cluster system for this the current region
     */
    private ActorSystem createClusterSystem(Config config) {

        // there is only 1 akka system for a Usergrid cluster
        final String clusterName = getClusterName();

        if (clusterSystem == null) {

            logger.info("Class: {}. ActorSystem [{}] not initialized, creating...", this, clusterName);

            clusterSystem = ActorSystem.create(clusterName, config);

            for (RouterProducer routerProducer : routerProducers) {
                logger.info("Creating router [{}] for region [{}]", routerProducer.getRouterPath(), currentRegion);
                routerProducer.produceRouter(clusterSystem, "io");
            }

            for (RouterProducer routerProducer : routerProducers) {
                Iterator<Class> messageTypes = routerProducer.getMessageTypes().iterator();
                while (messageTypes.hasNext()) {
                    Class messageType = messageTypes.next();
                    routersByMessageType.put(messageType, routerProducer.getRouterPath());
                }
            }

            //add a shutdown hook to clean all actor systems if the JVM exits without the servlet container knowing
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    leaveCluster();
                }
            });

        }

        return clusterSystem;
    }

    /**
     * Create RequestActor for each region.
     */
    private void createClientActors(ActorSystem system) {

        for (String region : getSeedsByRegion().keySet()) {

            if (currentRegion.equals(region)) {

                logger.info("Creating clientActor for region [{}]", region);

                // Each clientActor needs to know path to ClusterSingletonProxy and region
                clientActor = system.actorOf(Props.create(ClientActor.class, routersByMessageType), "clientActor");

                ClusterClientReceptionist.get(system).registerService(clientActor);

            } else {

                List<String> regionSeeds = getSeedsByRegion().get(region);
                Set<ActorPath> seedPaths = new HashSet<>(20);
                for (String seed : getSeedsByRegion().get(region)) {
                    seedPaths.add(ActorPaths.fromString(seed + "/system/receptionist"));
                }

                ActorRef clusterClient = system.actorOf(
                        ClusterClient.props(ClusterClientSettings.create(system).withInitialContacts(seedPaths)),
                        "client");

                clusterClientsByRegion.put(region, clusterClient);
            }
        }
    }

    @Override
    public void waitForClientActor() {

        waitForClientActor(clientActor);
    }

    private void waitForClientActor(ActorRef ra) {

        logger.info("Waiting on RequestActor [{}]...", ra.path());

        started = false;

        int retries = 0;
        int maxRetries = 60;
        while (retries < maxRetries) {
            Timeout t = new Timeout(10, TimeUnit.SECONDS);

            Future<Object> fut = Patterns.ask(ra, new ClientActor.StatusRequest(), t);
            try {
                ClientActor.StatusMessage result = (ClientActor.StatusMessage) Await.result(fut, t.duration());

                if (result.getStatus().equals(ClientActor.StatusMessage.Status.READY)) {
                    started = true;
                    break;
                }
                logger.info("Waiting for RequestActor [{}] region [{}] for [{}s]", ra.path(), currentRegion,
                        retries);
                Thread.sleep(1000);

            } catch (Exception e) {
                logger.error("Error: Timeout waiting for RequestActor [{}]", ra.path());
            }
            retries++;
        }

        if (started) {
            logger.info("RequestActor [{}] has started", ra.path());
        } else {
            throw new RuntimeException("RequestActor [" + ra.path() + "] did not start in time");
        }
    }

    @Override
    public void leaveCluster() {

        Cluster cluster = Cluster.get(clusterSystem);
        logger.info("Downing self: {} from cluster: {}", cluster.selfAddress(), clusterSystem.name());
        cluster.leave(cluster.selfAddress());
    }

}