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

io.brooklyn.entity.nosql.etcd.EtcdNodeSshDriver Maven / Gradle / Ivy

/*
 * 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 io.brooklyn.entity.nosql.etcd;

import static java.lang.String.format;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.OsDetails;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.entity.group.DynamicCluster;
import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.ssh.BashCommands;
import org.apache.brooklyn.util.text.Strings;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class EtcdNodeSshDriver extends AbstractSoftwareProcessSshDriver implements EtcdNodeDriver {

    public EtcdNodeSshDriver(final EtcdNodeImpl entity, final SshMachineLocation machine) {
        super(entity, machine);

        entity.sensors().set(Attributes.LOG_FILE_LOCATION, getLogFileLocation());
    }

    @Override
    public EtcdNodeImpl getEntity() {
        return EtcdNodeImpl.class.cast(super.getEntity());
    }

    @Override
    public Set getPortsUsed() {
        return ImmutableSet.builder()
                .addAll(super.getPortsUsed())
                .addAll(getPortMap().values())
                .build();
    }

    protected Map getPortMap() {
        return MutableMap.of("clientPort", getEntity().getClientPort(), "peerPort", getEntity().getPeerPort());
    }

    @Override
    public void install() {
        OsDetails osDetails = getMachine().getMachineDetails().getOsDetails();
        if (!osDetails.isLinux()) {
            throw new IllegalStateException("Machine was not detected as linux: " + getMachine() +
                    " Details: " + getMachine().getMachineDetails().getOsDetails());
        }

        List urls = resolver.getTargets();
        String saveAs = resolver.getFilename();
        List commands = Lists.newArrayList();

        commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
        commands.add(BashCommands.INSTALL_TAR);
        commands.add(String.format("tar xvzf %s", saveAs));

        newScript(INSTALLING)
                .body.append(commands)
                .failIfBodyEmpty()
                .failOnNonZeroResultCode()
                .execute();
    }

    @Override
    public void customize() {
        newScript(CUSTOMIZING).execute();

        // Set flag to indicate server has been installed
        entity.sensors().set(EtcdNode.ETCD_NODE_INSTALLED, Boolean.TRUE);
    }

    @Override
    public void launch() {
        DynamicTasks.queueIfPossible(DependentConfiguration.attributeWhenReady(entity, EtcdNode.ETCD_NODE_HAS_JOINED_CLUSTER))
                .orSubmitAndBlock(entity)
                .andWaitForSuccess();

        // Set default values for etcd startup command
        boolean clustered = Optional.fromNullable(entity.sensors().get(DynamicCluster.CLUSTER_MEMBER)).or(false);
        boolean first = entity.config().get(EtcdNode.IS_FIRST);
        String state = (first || !clustered) ? "new" : "existing";
        Collection advertisePeerUrls = getAdvertisePeerUrls();
        String nodes;
        if (clustered) {
            Entity cluster = entity.sensors().get(EtcdCluster.CLUSTER);
            nodes = cluster.sensors().get(EtcdCluster.NODE_LIST);
        } else {
            String prefix = getEntity().getNodeName()  + "=";
            nodes = prefix + Joiner.on("," + prefix).join(advertisePeerUrls);
        }

        // Build etcd startup command
        List commands = Lists.newLinkedList();
        commands.add("cd " + getRunDir());
        commands.add(format("%s --listen-client-urls %s --advertise-client-urls %s "
                + "--listen-peer-urls %s --initial-advertise-peer-urls %s "
                + "--initial-cluster-token %s -name %s --initial-cluster-state %s "
                + "--initial-cluster %s "
                + "%s "
                + "> %s 2>&1 < /dev/null &",
                        Os.mergePathsUnix(getExpandedInstallDir(), "etcd"),
                        getListenClientUrls(), Joiner.on(",").join(getAdvertiseClientUrls()),
                        getListenPeerUrls(), Joiner.on(",").join(advertisePeerUrls),
                        getEntity().getClusterToken(), getEntity().getNodeName(), state, nodes,
                        getAdditionalOptions(),
                        getLogFileLocation()));

        newScript(ImmutableMap.of(USE_PID_FILE, true), LAUNCHING)
                .body.append(commands)
                .failOnNonZeroResultCode()
                .execute();
    }

    @Override
    public void stop() {
        leaveCluster(getEntity().getNodeName());

        newScript(ImmutableMap.of(USE_PID_FILE, true), STOPPING).execute();
    }

    @Override
    public boolean isRunning() {
        return newScript(ImmutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0;
    }

    protected String getEtcdCtlCommand() {
        return Os.mergePathsUnix(getExpandedInstallDir(), "etcdctl");
    }

    protected String getLogFileLocation() {
        return Os.mergePathsUnix(getRunDir(), "console.log");
    }

    protected String getAdditionalOptions() {
        return StringUtils.defaultString(entity.config().get(EtcdNode.ADDITIONAL_OPTIONS)).trim();
    }

    /** @deprecated since 2.1.0. Use {@link #getAdvertiseClientUrls()} instead. */
    @Deprecated
    protected String getClientUrl() {
        return String.format("%s://%s:%d", getEntity().getClientProtocol(), getAddress(), getEntity().getClientPort());
    }

    /** @deprecated since 2.1.0. Use {@link #getAdvertisePeerUrls()} instead. */
    @Deprecated
    protected String getPeerUrl() {
        return String.format("%s://%s:%d", getEntity().getPeerProtocol(), getSubnetAddress(), getEntity().getPeerPort());
    }

    protected Collection getAdvertiseClientUrls() {
        return ImmutableList.of(getClientUrl());
    }

    protected Collection getAdvertisePeerUrls() {
        return ImmutableList.of(getPeerUrl());
    }

    protected String getListenClientUrls() {
        return String.format("%s://0.0.0.0:%d", getEntity().getClientProtocol(), getEntity().getClientPort());
    }

    protected String getListenPeerUrls() {
        return String.format("%s://0.0.0.0:%d", getEntity().getPeerProtocol(), getEntity().getPeerPort());
    }

    @Override
    public void joinCluster(String nodeName, String nodeAddress) {
        List commands = Lists.newLinkedList();
        commands.add("cd " + getRunDir());
        commands.add(String.format("%s --peers %s member add %s %s", getEtcdCtlCommand(), getClientUrl(), nodeName, nodeAddress));
        newScript("joinCluster")
                .body.append(commands)
                .failOnNonZeroResultCode()
                .execute();
    }

    @Override
    public void leaveCluster(String nodeName) {
        List listMembersCommands = Lists.newLinkedList();
        listMembersCommands.add("cd " + getRunDir());
        listMembersCommands.add(String.format("%s --peers %s member list", getEtcdCtlCommand(), getClientUrl()));
        ScriptHelper listMembersScript = newScript("listMembers")
                .body.append(listMembersCommands)
                .gatherOutput();
        int result = listMembersScript.execute();
        if (result != 0) {
            log.warn("{}: The 'member list' command on etcd '{}' failed: {}", new Object[] { entity, getClientUrl(), result });;
            log.debug("{}: STDOUT: {}", entity, listMembersScript.getResultStdout());
            log.debug("{}: STDERR: {}", entity, listMembersScript.getResultStderr());
            return; // Do not throw exception as may not be possible during shutdown
        }
        String output = listMembersScript.getResultStdout();
        Iterable found = Iterables.filter(Splitter.on(CharMatcher.anyOf("\r\n")).split(output), Predicates.containsPattern("name="));
        Optional node = Iterables.tryFind(found, Predicates.containsPattern(nodeName));
        if (Iterables.size(found) > 1 && node.isPresent()) {
            String nodeId = Strings.getFirstWord(node.get()).replace(":", "");
            log.debug("{}: Removing etcd node {} with id {} from {}", new Object[] { entity, nodeName, nodeId, getClientUrl() });

            List removeMemberCommands = Lists.newLinkedList();
            removeMemberCommands.add("cd " + getRunDir());
            removeMemberCommands.add(String.format("%s --peers %s member remove %s", getEtcdCtlCommand(), getClientUrl(), nodeId));
            newScript("removeMember")
                    .body.append(removeMemberCommands)
                    .failOnNonZeroResultCode()
                    .execute();
        } else {
            log.warn("{}: {} is not part of an etcd cluster", entity, nodeName);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy