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

com.yahoo.gondola.Config Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015, Yahoo Inc.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
package com.yahoo.gondola;

import com.typesafe.config.ConfigObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

/**
 * This class supplies all of the configuration information for the entire raft system.
 * This implementation reads a config file but can be subclassed to pull configuration data from elsewhere.
 */
public class Config {
    final static Logger logger = LoggerFactory.getLogger(Config.class);
    SecretHelper secretHelper;

    /*
     * This class encapsulates all the configuration data. It's used to avoid having a lock
     * in order to retrieve the data. A new ConfigData object is created with the new data and
     * then the new ConfigData object replaces the old one by simply setting a reference.
     */
    class ConfigData {
        com.typesafe.config.Config cfg;

        /// hostId -> member[]
        Map> hostToMembers = new HashMap<>();

        // hostId -> clusterIds
        Map> hostToClusters = new HashMap<>();

        // clusterId -> member[]
        Map> clusterToMembers = new HashMap<>();

        // hostId -> address
        Map addrs = new HashMap<>();

        // memberId -> member
        Map members = new HashMap<>();

        // hostId -> HostAttributes
        Map> hostAttributes = new HashMap<>();
    }

    // Holds the latest verison of the config data. When new data is available, the new ConfigData
    // is set into this variable
    ConfigData configData;

    // Listeners for config changes
    Observable observable = new Observable() {
        @Override
        public void notifyObservers() {
            // Hack to set the change flag so that the notification will take place
            super.setChanged();
            super.notifyObservers(Config.this);
        }
    };

    // The number of ms to check the config file for changes
    int watchPeriod;

    // If non-null, the file which contains the config information
    File file;

    public static class ConfigMember {
        String clusterId;
        String hostId;
        int memberId;

        ConfigMember(String clusterId, String hostId, int memberId) {
            this.clusterId = clusterId;
            this.hostId = hostId;
            this.memberId = memberId;
        }
    }

    /**
     * Reads config values from a HOCON file.
     * The observers are notified whenever the config file changes.
     */
    public Config(File file) {
        this.file = file;
        com.typesafe.config.Config cfg = com.typesafe.config.ConfigFactory.parseFile(file).resolve();
        process(cfg);
        new Watcher(file).start();
    }

    /**
     * Returns a string which describes the location of the config values. E.g. if the configs were retrieved from a file,
     * this would return the file location. Used in config-related error messages.
     *
     * @return a non-null string identifying the location of the configs.
     */
    public String getIdentifier() {
        return file == null ? "unknown" : file.toString();
    }

    /********************* simple property *******************/

    public String get(String property) {
        String secret = getSecret(property);
        return secret == null ? configData.cfg.getString(property) : secret;
    }

    public boolean getBoolean(String property) {
        String secret = getSecret(property);
        return secret == null ? configData.cfg.getBoolean(property) : Boolean.parseBoolean(secret);
    }

    public int getInt(String property) {
        String secret = getSecret(property);
        return secret == null ? configData.cfg.getInt(property) : Integer.parseInt(secret);
    }

    public long getLong(String property) {
        String secret = getSecret(property);
        return secret == null ? configData.cfg.getLong(property) : Long.parseLong(secret);
    }

    private String getSecret(String property) {
        return secretHelper == null ? null : secretHelper.getSecret(property);
    }

    /********************** listener *******************/

    /**
     * The observer's update() method will be called whenever the config file is changed and reloaded.
     * The arg will be this Config object.
     */
    public void registerForUpdates(Observer obs) {
        observable.addObserver(obs);
        obs.update(observable, this);
    }

    public void registerSecretHelper(SecretHelper helper) {

    }

    /********************* host and cluster *******************/

    public Set getHostIds() {
        return configData.hostToClusters.keySet();
    }

    /**
     * Returns the cluster ids running on a host.
     */
    public List getClusterIds(String hostId) {
        List clusters = configData.hostToClusters.get(hostId);
        if (clusters == null) {
            throw new IllegalArgumentException(String.format("host id '%s' not found in config", hostId));
        }
        return clusters;
    }

    /**
     * Returns all the members that are part of the cluster.
     * The first member returned is the primary member for that cluster.
     */
    public List getMembersInCluster(String clusterId) {
        List members = configData.clusterToMembers.get(clusterId);
        if (members == null) {
            throw new IllegalArgumentException(String.format("cluster '%s' not found in config", clusterId));
        }
        return members;
    }

    public InetSocketAddress getAddressForMember(int memberId) {
        ConfigMember cm = configData.members.get(memberId);
        if (cm == null) {
            throw new IllegalArgumentException(String.format("member '%s' not found in config", memberId));
        }
        return getAddressForHost(cm.hostId);
    }

    public InetSocketAddress getAddressForHost(String hostId) {
        InetSocketAddress addr = configData.addrs.get(hostId);
        if (addr == null) {
            throw new IllegalArgumentException(String.format("host id '%s' not found in config", hostId));
        }
        return addr;
    }

    public Map getAttributesForHost(String hostId) {
        return configData.hostAttributes.get(hostId);
    }

    private void process(com.typesafe.config.Config cfg) {


        // Prepare the container for the new config data
        ConfigData cd = new ConfigData();
        cd.cfg = cfg;
        cd.hostToMembers = new HashMap<>();
        cd.hostToClusters = new HashMap<>();
        cd.clusterToMembers = new HashMap<>();
        cd.addrs = new HashMap<>();
        cd.members = new HashMap<>();
        cd.hostAttributes = new HashMap<>();


        // Parse the clusterid, hostid, and memberid information
        for (com.typesafe.config.Config v : cfg.getConfigList("gondola.clusters")) {
            for (com.typesafe.config.Config h : v.getConfigList("hosts")) {
                ConfigMember cm = new ConfigMember(v.getString("clusterId"), h.getString("hostId"), h.getInt("memberId"));
                // update host to members
                List cmembers = cd.hostToMembers.get(cm.clusterId);
                if (cmembers == null) {
                    cmembers = new ArrayList<>();
                    cd.hostToMembers.put(cm.clusterId, cmembers);
                }
                cmembers.add(cm);

                // Update clusterToMembers
                cmembers = cd.clusterToMembers.get(cm.clusterId);
                if (cmembers == null) {
                    cmembers = new ArrayList<>();
                    cd.clusterToMembers.put(cm.clusterId, cmembers);
                }
                cmembers.add(cm);

                // Update hostToClusters
                List names = cd.hostToClusters.get(cm.hostId);
                if (names == null) {
                    names = new ArrayList<>();
                    cd.hostToClusters.put(cm.hostId, names);
                }
                names.add(cm.clusterId);
                cd.members.put(cm.memberId, cm);
            }
        }

        // Get host attribute map
        for (ConfigObject h : cfg.getObjectList("gondola.hosts")) {
            String hostId = String.valueOf(h.get("hostId").unwrapped());
            Map hostAttribute = new HashMap<>();
            cd.hostAttributes.put(hostId, hostAttribute);
            for (String key : h.keySet()) {
                hostAttribute.put(key, String.valueOf(h.get(key).unwrapped()));
            }
        }

        // Get all the addresses of the hosts
        for (com.typesafe.config.Config c : cfg.getConfigList("gondola.hosts")) {
            cd.addrs.put(c.getString("hostId"), new InetSocketAddress(c.getString("host"), c.getInt("port")));
        }

        // Enable the new config data
        this.configData = cd;
        watchPeriod = getInt("gondola.config_reload_period");
    }

    public class Watcher extends Thread {
        final File file;

        public Watcher(File file) {
            this.file = file;
            setName("ConfigWatcher");
            setDaemon(true);
        }

        @Override
        public void run() {
            long lastModified = file.lastModified();
            while (true) {
                try {
                    sleep(watchPeriod);

                    if (file.lastModified() != lastModified) {
                        // Parse and update observers
                        process(com.typesafe.config.ConfigFactory.parseFile(file));
                        observable.notifyObservers();

                        logger.info("Reloaded config file {}", file);
                        lastModified = file.lastModified();
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * This interface helps getting secret in the config and it's not allowed to store in the config.
     */
    public interface SecretHelper {
        /**
         * Get secret based on the property ID.
         * @param property The secret key
         * @return secret string, null if property doesn't exists
         */
        String getSecret(String property);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy