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

org.apache.brooklyn.entity.nosql.etcd.EtcdClusterImpl Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
/*
 * Copyright 2014-2016 by Cloudsoft Corporation Limited
 *
 * 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 org.apache.brooklyn.entity.nosql.etcd;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

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

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
import org.apache.brooklyn.entity.group.Cluster;
import org.apache.brooklyn.entity.group.DynamicCluster;
import org.apache.brooklyn.entity.group.DynamicClusterImpl;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;

public class EtcdClusterImpl extends DynamicClusterImpl implements EtcdCluster {

    private static final Logger log = LoggerFactory.getLogger(EtcdClusterImpl.class);

    private transient Object memberMutex = new Object[0]; // For cluster membership management
    private transient Object clusterMutex = new Object[0]; // For cluster join/leave operations

    @Override
    public Object getClusterMutex() { return clusterMutex; }

    @Override
    public String getIconUrl() { return "https://s3.amazonaws.com/cloud.ohloh.net/attachments/85177/etcd-glyph-color_med.png"; }

    public void init() {
        super.init();

        sensors().set(NODE_ID, new AtomicInteger(0));
        ConfigToAttributes.apply(this, ETCD_NODE_SPEC);
        config().set(MEMBER_SPEC, sensors().get(ETCD_NODE_SPEC));
    }

    @Override
    public void start(Collection locs) {
        addLocations(locs);
        List locations = MutableList.copyOf(Locations.getLocationsCheckingAncestors(locs, this));

        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);

        connectSensors();

        super.start(locations);

        Optional anyNode = Iterables.tryFind(getMembers(),Predicates.and(
                Predicates.instanceOf(EtcdNode.class),
                EntityPredicates.attributeEqualTo(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, true),
                EntityPredicates.attributeEqualTo(Startable.SERVICE_UP, true)));
        if (config().get(Cluster.INITIAL_SIZE) == 0 || anyNode.isPresent()) {
            sensors().set(Startable.SERVICE_UP, true);
            ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
        } else {
            log.warn("No Etcd nodes are found on the cluster: {}. Initialization Failed", getId());
            ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
        }
    }

    protected void connectSensors() {
        policies().add(PolicySpec.create(MemberTrackingPolicy.class)
                .displayName("EtcdCluster node tracker")
                .configure("sensorsToTrack", ImmutableSet.of(Attributes.HOSTNAME))
                .configure("group", this));
    }

    public boolean isProxied() {
        String memberType = config().get(MEMBER_SPEC).getType().getSimpleName();
        return memberType.contains("Proxy");
    }

    protected void onServerPoolMemberChanged(Entity member) {
        synchronized (memberMutex) {
            log.debug("For {}, considering membership of {} which is in locations {}", new Object[]{ this, member, member.getLocations() });

            Map nodes = sensors().get(ETCD_CLUSTER_NODES);
            if (belongsInServerPool(member)) {
                if (nodes == null) {
                    nodes = Maps.newLinkedHashMap();
                }
                String name = Preconditions.checkNotNull(getNodeName(member));

                // Wait until node has been installed
                DynamicTasks.queueIfPossible(DependentConfiguration.attributeWhenReady(member, EtcdNode.ETCD_NODE_INSTALLED))
                        .orSubmitAndBlock(this)
                        .andWaitForSuccess();

                // Check for first node in the cluster.
                Duration timeout = config().get(BrooklynConfigKeys.START_TIMEOUT);
                Entity firstNode = sensors().get(DynamicCluster.FIRST);
                if (member.equals(firstNode)) {
                    nodes.put(member, name);
                    recalculateClusterAddresses(nodes);
                    log.info("Adding first node {}: {}; {} to cluster", new Object[] { this, member, name });
                    ((EntityInternal) member).sensors().set(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, Boolean.TRUE);
                } else {
                    int retry = 3; // TODO use a configurable Repeater instead?
                    while (retry --> 0 && member.sensors().get(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER) == null && !nodes.containsKey(member)) {
                        Optional anyNodeInCluster = Iterables.tryFind(nodes.keySet(), Predicates.and(
                                Predicates.instanceOf(EtcdNode.class),
                                EntityPredicates.attributeEqualTo(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, Boolean.TRUE)));
                        if (anyNodeInCluster.isPresent()) {
                            DynamicTasks.queueIfPossible(DependentConfiguration.builder().attributeWhenReady(anyNodeInCluster.get(), Startable.SERVICE_UP).timeout(timeout).build())
                                    .orSubmitAndBlock(this)
                                    .andWaitForSuccess();
                            Entities.invokeEffectorWithArgs(this, anyNodeInCluster.get(), EtcdNode.JOIN_ETCD_CLUSTER, name, getNodeAddress(member)).blockUntilEnded(timeout);
                            nodes.put(member, name);
                            recalculateClusterAddresses(nodes);
                            log.info("Adding node {}: {}; {} to cluster", new Object[] { this, member, name });
                            ((EntityInternal) member).sensors().set(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, Boolean.TRUE);
                        } else {
                            log.info("Waiting for first node in cluster {}", this);
                            Time.sleep(Duration.seconds(15));
                        }
                    }
                }
            } else {
                if (nodes != null && nodes.containsKey(member)) {
                    Optional anyNodeInCluster = Iterables.tryFind(nodes.keySet(), Predicates.and(
                            Predicates.instanceOf(EtcdNode.class),
                            EntityPredicates.attributeEqualTo(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, Boolean.TRUE),
                            Predicates.not(Predicates.equalTo(member))));
                    if (anyNodeInCluster.isPresent()) {
                        Entities.invokeEffectorWithArgs(this, anyNodeInCluster.get(), EtcdNode.LEAVE_ETCD_CLUSTER, getNodeName(member)).blockUntilEnded();
                    }
                    nodes.remove(member);
                    recalculateClusterAddresses(nodes);
                    log.info("Removing node {}: {}; {} from cluster", new Object[] { this, member, getNodeName(member) });
                    ((EntityInternal) member).sensors().set(EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER, Boolean.FALSE);
                }
            }

            ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyMap(this, ETCD_CLUSTER_NODES);
            log.debug("Done {} checkEntity {}", this, member);
        }
    }

    private void recalculateClusterAddresses(Map nodes) {
        Map addresses = Maps.newHashMap();
        for (Entity entity : nodes.keySet()) {
            if (entity instanceof EtcdNode) {
                addresses.put(getNodeName(entity), getNodeAddress(entity));
            }
        }
        sensors().set(ETCD_CLUSTER_NODES, nodes);
        sensors().set(NODE_LIST, Joiner.on(",").withKeyValueSeparator("=").join(addresses));
    }

    protected boolean belongsInServerPool(Entity member) {
        if (member.sensors().get(Attributes.HOSTNAME) == null) {
            log.debug("Members of {}, checking {}, eliminating because hostname not yet set", this, member);
            return false;
        }
        if (!getMembers().contains(member)) {
            log.debug("Members of {}, checking {}, eliminating because not member", this, member);
            return false;
        }
        log.debug("Members of {}, checking {}, approving", this, member);
        return true;
    }

    private String getNodeName(Entity node) {
        return node.sensors().get(EtcdNode.ETCD_NODE_NAME);
    }

    private String getNodeAddress(Entity node) {
        return "http://" + node.sensors().get(Attributes.SUBNET_ADDRESS) + ":" + node.sensors().get(EtcdNode.ETCD_PEER_PORT);
    }

    public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
        @Override
        protected void onEntityEvent(EventType type, Entity target) {
            ((EtcdClusterImpl) entity).onServerPoolMemberChanged(target);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy