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

com.proofpoint.discovery.elb.ElasticLoadBalancerUpdater Maven / Gradle / Ivy

/*
 * Copyright 2011 Proofpoint, Inc.
 *
 * 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.proofpoint.discovery.elb;

import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancing;
import com.amazonaws.services.elasticloadbalancing.model.DeregisterInstancesFromLoadBalancerRequest;
import com.amazonaws.services.elasticloadbalancing.model.Instance;
import com.amazonaws.services.elasticloadbalancing.model.Listener;
import com.amazonaws.services.elasticloadbalancing.model.ListenerDescription;
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription;
import com.amazonaws.services.elasticloadbalancing.model.RegisterInstancesWithLoadBalancerRequest;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.proofpoint.discovery.client.DiscoveryLookupClient;
import com.proofpoint.discovery.client.ServiceDescriptor;
import com.proofpoint.discovery.client.ServiceDescriptors;
import com.proofpoint.log.Logger;
import com.proofpoint.node.NodeInfo;
import com.proofpoint.units.Duration;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;

@Immutable
public class ElasticLoadBalancerUpdater
{
    private static final Logger log = Logger.get(ElasticLoadBalancerUpdater.class);

    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(
            new ThreadFactoryBuilder().setNameFormat("ElasticLoadBalancerUpdater-%s").build());

    private final AmazonElasticLoadBalancing elbClient;
    private final NodeInfo nodeInfo;
    private final DiscoveryLookupClient discoveryClient;
    private final Duration updateInterval;

    @Inject
    public ElasticLoadBalancerUpdater(AmazonElasticLoadBalancing elbClient, NodeInfo nodeInfo, DiscoveryLookupClient discoveryClient, ServerConfig config)
    {
        this.elbClient = checkNotNull(elbClient, "elbClient is null");
        this.nodeInfo = checkNotNull(nodeInfo, "nodeInfo is null");
        this.discoveryClient = checkNotNull(discoveryClient, "discoveryClient is null");
        checkNotNull(config, "config is null");
        updateInterval = config.getUpdateInterval();
    }

    @PostConstruct
    public void start()
    {
        Runnable updater = new Runnable()
        {
            @Override
            public void run()
            {
                try {
                    update();
                }
                catch (Exception e) {
                    log.error(e, "update failed");
                }
            }
        };
        executor.scheduleAtFixedRate(updater, 0, (long) updateInterval.toMillis(), TimeUnit.MILLISECONDS);
    }

    @PreDestroy
    public void destroy()
            throws IOException
    {
        executor.shutdown();
    }

    public void update()
            throws Exception
    {
        for (LoadBalancerDescription loadBalancer : elbClient.describeLoadBalancers().getLoadBalancerDescriptions()) {
            // split ELB name into parts
            String elbName = loadBalancer.getLoadBalancerName();
            List parts = copyOf(Splitter.on('-').split(elbName).iterator());
            if (parts.size() != 3 && parts.size() != 4) {
                log.debug("ignoring load balancer: %s", elbName);
                continue;
            }
            String environment = parts.get(0);
            String type = parts.get(1);
            String pool = parts.get(2);
            log.debug("found load balancer: %s", elbName);

            // check against environment
            if (!environment.equals(nodeInfo.getEnvironment())) {
                continue;
            }

            // look for services in discovery
            ServiceDescriptors services = discoveryClient.getServices(type, pool).get(1, TimeUnit.SECONDS);

            // map services to instances
            Set instances = newHashSet();
            for (ServiceDescriptor descriptor : services.getServiceDescriptors()) {
                String instanceId = extractEc2InstanceId(descriptor.getLocation());
                if (instanceId == null) {
                    log.warn("invalid EC2 location: %s", descriptor.getLocation());
                    continue;
                }

                // verify load balancer listeners against the service announcement
                boolean valid = true;
                for (Listener listener : transform(loadBalancer.getListenerDescriptions(), GET_LISTENER)) {
                    if (!serviceExistsForListener(listener, descriptor.getProperties())) {
                        valid = false;
                        log.warn("load balancer %s listener %s does not match service %s", elbName, listener, descriptor);
                    }
                }
                if (valid) {
                    instances.add(instanceId);
                }
            }

            // get registered instances
            Set registeredInstances = newHashSet(transform(loadBalancer.getInstances(), GET_INSTANCE_ID));

            // add new instances to load balancer
            Collection addInstances = difference(instances, registeredInstances);
            if (!addInstances.isEmpty()) {
                registerInstances(elbName, addInstances);
            }

            // remove missing instances from load balancer
            Collection removeInstances = difference(registeredInstances, instances);
            if (!removeInstances.isEmpty()) {
                deregisterInstances(elbName, removeInstances);
            }
        }
    }

    private static boolean serviceExistsForListener(Listener listener, Map properties)
    {
        String protocol = listener.getInstanceProtocol().toLowerCase();
        if ((!protocol.equals("http")) && (!protocol.equals("https"))) {
            return false;
        }
        URI uri = extractUri(properties.get(protocol));
        return (uri != null) && (uri.getPort() == listener.getInstancePort());
    }

    private void registerInstances(String elbName, Collection instanceIds)
    {
        List instances = copyOf(transform(instanceIds, NEW_INSTANCE));
        log.debug("adding instances to load balancer: %s: %s", elbName, instanceIds);
        elbClient.registerInstancesWithLoadBalancer(new RegisterInstancesWithLoadBalancerRequest(elbName, instances));
        log.info("added instances to load balancer: %s: %s", elbName, instanceIds);
    }

    private void deregisterInstances(String elbName, Collection instanceIds)
    {
        List instances = copyOf(transform(instanceIds, NEW_INSTANCE));
        log.debug("removing instances from load balancer: %s: %s", elbName, instanceIds);
        elbClient.deregisterInstancesFromLoadBalancer(new DeregisterInstancesFromLoadBalancerRequest(elbName, instances));
        log.info("removed instances from load balancer: %s: %s", elbName, instanceIds);
    }

    private static String extractEc2InstanceId(String location)
    {
        try {
            return Ec2Location.valueOf(location).getInstanceId();
        }
        catch (IllegalArgumentException e) {
            return null;
        }
    }

    private static URI extractUri(String uri)
    {
        try {
            return new URI(uri);
        }
        catch (Exception e) {
            return null;
        }
    }

    private static Function GET_LISTENER = new Function()
    {
        @Override
        public Listener apply(ListenerDescription input)
        {
            return input.getListener();
        }
    };

    private static Function GET_INSTANCE_ID = new Function()
    {
        @Override
        public String apply(Instance input)
        {
            return input.getInstanceId();
        }
    };

    private static Function NEW_INSTANCE = new Function()
    {

        @Override
        public Instance apply(String input)
        {
            return new Instance(input);
        }
    };
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy