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

org.apache.brooklyn.entity.nosql.cassandra.CassandraDatacenterImpl Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * 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.brooklyn.entity.nosql.cassandra;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.core.effector.EffectorBody;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
import org.apache.brooklyn.core.location.Machines;
import org.apache.brooklyn.enricher.stock.Enrichers;
import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
import org.apache.brooklyn.entity.group.DynamicClusterImpl;
import org.apache.brooklyn.entity.group.DynamicGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Time;

import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.net.HostAndPort;

/**
 * Implementation of {@link CassandraDatacenter}.
 * 

* Several subtleties to note: * - a node may take some time after it is running and serving JMX to actually be contactable on its thrift port * (so we wait for thrift port to be contactable) * - sometimes new nodes take a while to peer, and/or take a while to get a consistent schema * (each up to 1m; often very close to the 1m) */ public class CassandraDatacenterImpl extends DynamicClusterImpl implements CassandraDatacenter { /* * TODO Seed management is hard! * - The ServiceRestarter is not doing customize(), so is not refreshing the seeds in cassandra.yaml. * If we have two nodes that were seeds for each other and they both restart at the same time, we'll have a split brain. */ private static final Logger log = LoggerFactory.getLogger(CassandraDatacenterImpl.class); // Mutex for synchronizing during re-size operations private final Object mutex = new Object[0]; private final Supplier> defaultSeedSupplier = new Supplier>() { // Mutex for (re)calculating our seeds // TODO is this very dangerous?! Calling out to SeedTracker, which calls out to alien getAttribute()/getConfig(). But I think that's ok. // TODO might not need mutex? previous race was being caused by something else, other than concurrent calls! private final Object seedMutex = new Object(); @Override public Set get() { synchronized (seedMutex) { boolean hasPublishedSeeds = Boolean.TRUE.equals(getAttribute(HAS_PUBLISHED_SEEDS)); int quorumSize = getSeedQuorumSize(); Set potentialSeeds = gatherPotentialSeeds(); Set potentialRunningSeeds = gatherPotentialRunningSeeds(); boolean stillWaitingForQuorum = (!hasPublishedSeeds) && (potentialSeeds.size() < quorumSize); if (stillWaitingForQuorum) { if (log.isDebugEnabled()) log.debug("Not refreshed seeds of cluster {}, because still waiting for quorum (need {}; have {} potentials)", new Object[] {CassandraDatacenterImpl.class, quorumSize, potentialSeeds.size()}); return ImmutableSet.of(); } else if (hasPublishedSeeds) { Set currentSeeds = getAttribute(CURRENT_SEEDS); if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) { if (Sets.intersection(currentSeeds, potentialSeeds).isEmpty()) { log.warn("Cluster {} lost all its seeds while starting! Subsequent failure likely, but changing seeds during startup would risk split-brain: seeds={}", new Object[] {CassandraDatacenterImpl.this, currentSeeds}); } return currentSeeds; } else if (potentialRunningSeeds.isEmpty()) { // TODO Could be race where nodes have only just returned from start() and are about to // transition to serviceUp; so don't just abandon all our seeds! log.warn("Cluster {} has no running seeds (yet?); leaving seeds as-is; but risks split-brain if these seeds come back up!", new Object[] {CassandraDatacenterImpl.this}); return currentSeeds; } else { Set result = trim(quorumSize, potentialRunningSeeds); log.debug("Cluster {} updating seeds: chosen={}; potentialRunning={}", new Object[] {CassandraDatacenterImpl.this, result, potentialRunningSeeds}); return result; } } else { Set result = trim(quorumSize, potentialSeeds); if (log.isDebugEnabled()) log.debug("Cluster {} has reached seed quorum: seeds={}", new Object[] {CassandraDatacenterImpl.this, result}); return result; } } } private Set trim(int num, Set contenders) { // Prefer existing seeds wherever possible; otherwise accept any other contenders Set currentSeeds = (getAttribute(CURRENT_SEEDS) != null) ? getAttribute(CURRENT_SEEDS) : ImmutableSet.of(); Set result = Sets.newLinkedHashSet(); result.addAll(Sets.intersection(currentSeeds, contenders)); result.addAll(contenders); return ImmutableSet.copyOf(Iterables.limit(result, num)); } }; protected SeedTracker seedTracker = new SeedTracker(); protected TokenGenerator tokenGenerator = null; public CassandraDatacenterImpl() { } @Override public void init() { super.init(); /* * subscribe to hostname, and keep an accurate set of current seeds in a sensor; * then at nodes we set the initial seeds to be the current seeds when ready (non-empty) */ subscriptions().subscribeToMembers(this, Attributes.HOSTNAME, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { seedTracker.onHostnameChanged(event.getSource(), event.getValue()); } }); subscriptions().subscribe(this, DynamicGroup.MEMBER_REMOVED, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { seedTracker.onMemberRemoved(event.getValue()); } }); subscriptions().subscribeToMembers(this, Attributes.SERVICE_UP, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { seedTracker.onServiceUpChanged(event.getSource(), event.getValue()); } }); subscriptions().subscribeToMembers(this, Attributes.SERVICE_STATE_ACTUAL, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { // trigger a recomputation also when lifecycle state changes, // because it might not have ruled a seed as inviable when service up went true // because service state was not yet running seedTracker.onServiceUpChanged(event.getSource(), Lifecycle.RUNNING==event.getValue()); } }); // Track the datacenters for this cluster subscriptions().subscribeToMembers(this, CassandraNode.DATACENTER_NAME, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { Entity member = event.getSource(); String dcName = event.getValue(); if (dcName != null) { Multimap datacenterUsage = getAttribute(DATACENTER_USAGE); Multimap mutableDatacenterUsage = (datacenterUsage == null) ? LinkedHashMultimap.create() : LinkedHashMultimap.create(datacenterUsage); Optional oldDcName = getKeyOfVal(mutableDatacenterUsage, member); if (!(oldDcName.isPresent() && dcName.equals(oldDcName.get()))) { mutableDatacenterUsage.values().remove(member); mutableDatacenterUsage.put(dcName, member); sensors().set(DATACENTER_USAGE, mutableDatacenterUsage); sensors().set(DATACENTERS, Sets.newLinkedHashSet(mutableDatacenterUsage.keySet())); } } } private Optional getKeyOfVal(Multimap map, V val) { for (Map.Entry entry : map.entries()) { if (Objects.equal(val, entry.getValue())) { return Optional.of(entry.getKey()); } } return Optional.absent(); } }); subscriptions().subscribe(this, DynamicGroup.MEMBER_REMOVED, new SensorEventListener() { @Override public void onEvent(SensorEvent event) { Entity entity = event.getSource(); Multimap datacenterUsage = getAttribute(DATACENTER_USAGE); if (datacenterUsage != null && datacenterUsage.containsValue(entity)) { Multimap mutableDatacenterUsage = LinkedHashMultimap.create(datacenterUsage); mutableDatacenterUsage.values().remove(entity); sensors().set(DATACENTER_USAGE, mutableDatacenterUsage); sensors().set(DATACENTERS, Sets.newLinkedHashSet(mutableDatacenterUsage.keySet())); } } }); getMutableEntityType().addEffector(EXECUTE_SCRIPT, new EffectorBody() { @Override public String call(ConfigBag parameters) { return executeScript((String)parameters.getStringKey("commands")); } }); } protected Supplier> getSeedSupplier() { Supplier> seedSupplier = getConfig(SEED_SUPPLIER); return (seedSupplier == null) ? defaultSeedSupplier : seedSupplier; } protected boolean useVnodes() { return Boolean.TRUE.equals(getConfig(USE_VNODES)); } protected synchronized TokenGenerator getTokenGenerator() { if (tokenGenerator!=null) return tokenGenerator; try { tokenGenerator = getConfig(TOKEN_GENERATOR_CLASS).newInstance(); BigInteger shift = getConfig(TOKEN_SHIFT); if (shift==null) shift = BigDecimal.valueOf(Math.random()).multiply( new BigDecimal(tokenGenerator.range())).toBigInteger(); tokenGenerator.setOrigin(shift); return tokenGenerator; } catch (Exception e) { throw Throwables.propagate(e); } } protected int getSeedQuorumSize() { Integer quorumSize = getConfig(INITIAL_QUORUM_SIZE); if (quorumSize!=null && quorumSize>0) return quorumSize; // default 2 is recommended, unless initial size is smaller return Math.min(Math.max(getConfig(INITIAL_SIZE), 1), DEFAULT_SEED_QUORUM); } @Override public Set gatherPotentialSeeds() { return seedTracker.gatherPotentialSeeds(); } @Override public Set gatherPotentialRunningSeeds() { return seedTracker.gatherPotentialRunningSeeds(); } /** * Sets the default {@link #MEMBER_SPEC} to describe the Cassandra nodes. */ @Override protected EntitySpec getMemberSpec() { return getConfig(MEMBER_SPEC, EntitySpec.create(CassandraNode.class)); } @Override public String getClusterName() { return getAttribute(CLUSTER_NAME); } @Override public Collection grow(int delta) { if (useVnodes()) { // nothing to do for token generator } else { if (getCurrentSize() == 0) { getTokenGenerator().growingCluster(delta); } } return super.grow(delta); } @Override protected Entity createNode(@Nullable Location loc, Map flags) { Map allflags = MutableMap.copyOf(flags); if (flags.containsKey("token") || flags.containsKey("cassandra.token")) { // TODO Delete in future version; was deprecated in 0.7.0; deleted config key in 0.9.0 log.warn("Cassandra token no longer supported - use 'tokens' in "+CassandraDatacenterImpl.this); } if (flags.containsKey(CassandraNode.TOKENS) || flags.containsKey("tokens") || flags.containsKey("cassandra.tokens")) { // leave token config as-is } else if (!useVnodes()) { BigInteger token = getTokenGenerator().newToken(); if (token != null) { allflags.put(CassandraNode.TOKENS, ImmutableSet.of(token)); } } if ((flags.containsKey(CassandraNode.NUM_TOKENS_PER_NODE) || flags.containsKey("numTokensPerNode"))) { // leave num_tokens as-is } else if (useVnodes()) { Integer numTokensPerNode = getConfig(NUM_TOKENS_PER_NODE); allflags.put(CassandraNode.NUM_TOKENS_PER_NODE, numTokensPerNode); } else { allflags.put(CassandraNode.NUM_TOKENS_PER_NODE, 1); } return super.createNode(loc, allflags); } @Override protected Entity replaceMember(Entity member, Location memberLoc, Map extraFlags) { Set oldTokens = ((CassandraNode) member).getTokens(); Set newTokens = (oldTokens != null && oldTokens.size() > 0) ? getTokenGenerator().getTokensForReplacementNode(oldTokens) : null; MutableMap allFlags = MutableMap.copyOf(extraFlags).add(CassandraNode.TOKENS, newTokens); return super.replaceMember(member, memberLoc, allFlags); } @Override public void start(Collection locations) { Machines.warnIfLocalhost(locations, "CassandraCluster does not support multiple nodes on localhost, " + "due to assumptions Cassandra makes about the use of the same port numbers used across the cluster."); // force this to be set - even if it is using the default sensors().set(CLUSTER_NAME, getConfig(CLUSTER_NAME)); super.start(locations); connectSensors(); // TODO wait until all nodes which we think are up are consistent // i.e. all known nodes use the same schema, as reported by // SshEffectorTasks.ssh("echo \"describe cluster;\" | /bin/cassandra-cli"); // once we've done that we can revert to using 2 seed nodes. // see CassandraCluster.DEFAULT_SEED_QUORUM // (also ensure the cluster is ready if we are about to run a creation script) Time.sleep(getConfig(DELAY_BEFORE_ADVERTISING_CLUSTER)); String scriptUrl = getConfig(CassandraNode.CREATION_SCRIPT_URL); if (Strings.isNonEmpty(scriptUrl)) { executeScript(new ResourceUtils(this).getResourceAsString(scriptUrl)); } update(); } protected void connectSensors() { connectEnrichers(); policies().add(PolicySpec.create(MemberTrackingPolicy.class) .displayName("Cassandra Cluster Tracker") .configure("sensorsToTrack", ImmutableSet.of(Attributes.SERVICE_UP, Attributes.HOSTNAME, CassandraNode.THRIFT_PORT)) .configure("group", this)); } public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy { @Override protected void onEntityChange(Entity member) { if (log.isDebugEnabled()) log.debug("Node {} updated in Cluster {}", member, this); ((CassandraDatacenterImpl)entity).update(); } @Override protected void onEntityAdded(Entity member) { if (log.isDebugEnabled()) log.debug("Node {} added to Cluster {}", member, this); ((CassandraDatacenterImpl)entity).update(); } @Override protected void onEntityRemoved(Entity member) { if (log.isDebugEnabled()) log.debug("Node {} removed from Cluster {}", member, this); ((CassandraDatacenterImpl)entity).update(); } }; @SuppressWarnings("unchecked") protected void connectEnrichers() { List>> summingEnricherSetup = ImmutableList.of( ImmutableList.of(CassandraNode.READ_ACTIVE, READ_ACTIVE), ImmutableList.of(CassandraNode.READ_PENDING, READ_PENDING), ImmutableList.of(CassandraNode.WRITE_ACTIVE, WRITE_ACTIVE), ImmutableList.of(CassandraNode.WRITE_PENDING, WRITE_PENDING) ); List>> averagingEnricherSetup = ImmutableList.of( ImmutableList.of(CassandraNode.READS_PER_SECOND_LAST, READS_PER_SECOND_LAST_PER_NODE), ImmutableList.of(CassandraNode.WRITES_PER_SECOND_LAST, WRITES_PER_SECOND_LAST_PER_NODE), ImmutableList.of(CassandraNode.WRITES_PER_SECOND_IN_WINDOW, WRITES_PER_SECOND_IN_WINDOW_PER_NODE), ImmutableList.of(CassandraNode.READS_PER_SECOND_IN_WINDOW, READS_PER_SECOND_IN_WINDOW_PER_NODE), ImmutableList.of(CassandraNode.THRIFT_PORT_LATENCY, THRIFT_PORT_LATENCY_PER_NODE), ImmutableList.of(CassandraNode.THRIFT_PORT_LATENCY_IN_WINDOW, THRIFT_PORT_LATENCY_IN_WINDOW_PER_NODE), ImmutableList.of(CassandraNode.PROCESS_CPU_TIME_FRACTION_LAST, PROCESS_CPU_TIME_FRACTION_LAST_PER_NODE), ImmutableList.of(CassandraNode.PROCESS_CPU_TIME_FRACTION_IN_WINDOW, PROCESS_CPU_TIME_FRACTION_IN_WINDOW_PER_NODE) ); for (List> es : summingEnricherSetup) { AttributeSensor t = es.get(0); AttributeSensor total = es.get(1); enrichers().add(Enrichers.builder() .aggregating(t) .publishing(total) .fromMembers() .computingSum() .defaultValueForUnreportedSensors(null) .valueToReportIfNoSensors(null) .build()); } for (List> es : averagingEnricherSetup) { AttributeSensor t = (AttributeSensor) es.get(0); AttributeSensor average = (AttributeSensor) es.get(1); enrichers().add(Enrichers.builder() .aggregating(t) .publishing(average) .fromMembers() .computingAverage() .defaultValueForUnreportedSensors(null) .valueToReportIfNoSensors(null) .build()); } } @Override public void stop() { disconnectSensors(); super.stop(); } protected void disconnectSensors() { } @Override public void update() { synchronized (mutex) { // Update our seeds, as necessary seedTracker.refreshSeeds(); // Choose the first available cluster member to set host and port (and compute one-up) Optional upNode = Iterables.tryFind(getMembers(), EntityPredicates.attributeEqualTo(SERVICE_UP, Boolean.TRUE)); if (upNode.isPresent()) { sensors().set(HOSTNAME, upNode.get().getAttribute(Attributes.HOSTNAME)); sensors().set(THRIFT_PORT, upNode.get().getAttribute(CassandraNode.THRIFT_PORT)); List currentNodes = getAttribute(CASSANDRA_CLUSTER_NODES); Set oldNodes = (currentNodes != null) ? ImmutableSet.copyOf(currentNodes) : ImmutableSet.of(); Set newNodes = MutableSet.of(); for (Entity member : getMembers()) { if (member instanceof CassandraNode && Boolean.TRUE.equals(member.getAttribute(SERVICE_UP))) { String hostname = member.getAttribute(Attributes.HOSTNAME); Integer thriftPort = member.getAttribute(CassandraNode.THRIFT_PORT); if (hostname != null && thriftPort != null) { newNodes.add(HostAndPort.fromParts(hostname, thriftPort).toString()); } } } if (Sets.symmetricDifference(oldNodes, newNodes).size() > 0) { sensors().set(CASSANDRA_CLUSTER_NODES, MutableList.copyOf(newNodes)); } } else { sensors().set(HOSTNAME, null); sensors().set(THRIFT_PORT, null); sensors().set(CASSANDRA_CLUSTER_NODES, Collections.emptyList()); } ServiceNotUpLogic.updateNotUpIndicatorRequiringNonEmptyList(this, CASSANDRA_CLUSTER_NODES); } } /** * For tracking our seeds. This gets fiddly! High-level logic is: *

    *
  • If we have never reached quorum (i.e. have never published seeds), then continue to wait for quorum; * because entity-startup may be blocking for this. This is handled by the seedSupplier. *
  • If we previously reached quorum (i.e. have previousy published seeds), then always update; * we never want stale/dead entities listed in our seeds. *
  • If an existing seed looks unhealthy, then replace it. *
  • If a new potential seed becomes available (and we're in need of more), then add it. *
      * * Also note that {@link CassandraFabric} can take over, because it know about multiple sub-clusters! * It will provide a different {@link CassandraDatacenter#SEED_SUPPLIER}. Each time we think that our seeds * need to change, we call that. The fabric will call into {@link CassandraDatacenterImpl#gatherPotentialSeeds()} * to find out what's available. * * @author aled */ protected class SeedTracker { private final Map memberUpness = Maps.newLinkedHashMap(); public void onMemberRemoved(Entity member) { Set seeds = getSeeds(); boolean maybeRemove = seeds.contains(member); memberUpness.remove(member); if (maybeRemove) { refreshSeeds(); } else { if (log.isTraceEnabled()) log.trace("Seeds considered stable for cluster {} (node {} removed)", new Object[] {CassandraDatacenterImpl.this, member}); return; } } public void onHostnameChanged(Entity member, String hostname) { Set seeds = getSeeds(); int quorum = getSeedQuorumSize(); boolean isViable = isViableSeed(member); boolean maybeAdd = isViable && seeds.size() < quorum; boolean maybeRemove = seeds.contains(member) && !isViable; if (maybeAdd || maybeRemove) { refreshSeeds(); } else { if (log.isTraceEnabled()) log.trace("Seeds considered stable for cluster {} (node {} changed hostname {})", new Object[] {CassandraDatacenterImpl.this, member, hostname}); return; } } public void onServiceUpChanged(Entity member, Boolean serviceUp) { Boolean oldVal = memberUpness.put(member, serviceUp); if (Objects.equal(oldVal, serviceUp)) { if (log.isTraceEnabled()) log.trace("Ignoring duplicate service-up in "+CassandraDatacenterImpl.this+" for "+member+", "+serviceUp); } Set seeds = getSeeds(); int quorum = getSeedQuorumSize(); boolean isViable = isViableSeed(member); boolean maybeAdd = isViable && seeds.size() < quorum; boolean maybeRemove = seeds.contains(member) && !isViable; if (log.isDebugEnabled()) log.debug("Considering refresh of seeds for "+CassandraDatacenterImpl.this+" because "+member+" is now "+serviceUp+" ("+isViable+" / "+maybeAdd+" / "+maybeRemove+")"); if (maybeAdd || maybeRemove) { refreshSeeds(); } else { if (log.isTraceEnabled()) log.trace("Seeds considered stable for cluster {} (node {} changed serviceUp {})", new Object[] {CassandraDatacenterImpl.this, member, serviceUp}); return; } } protected Set getSeeds() { Set result = getAttribute(CURRENT_SEEDS); return (result == null) ? ImmutableSet.of() : result; } public void refreshSeeds() { Set oldseeds = getAttribute(CURRENT_SEEDS); Set newseeds = getSeedSupplier().get(); if (Objects.equal(oldseeds, newseeds)) { if (log.isTraceEnabled()) log.debug("Seed refresh no-op for cluster {}: still={}", new Object[] {CassandraDatacenterImpl.this, oldseeds}); } else { if (log.isDebugEnabled()) log.debug("Refreshing seeds of cluster {}: now={}; old={}", new Object[] {this, newseeds, oldseeds}); sensors().set(CURRENT_SEEDS, newseeds); if (newseeds != null && newseeds.size() > 0) { sensors().set(HAS_PUBLISHED_SEEDS, true); } } } public Set gatherPotentialSeeds() { Set result = Sets.newLinkedHashSet(); for (Entity member : getMembers()) { if (isViableSeed(member)) { result.add(member); } } if (log.isTraceEnabled()) log.trace("Viable seeds in Cluster {}: {}", new Object[] {result}); return result; } public Set gatherPotentialRunningSeeds() { Set result = Sets.newLinkedHashSet(); for (Entity member : getMembers()) { if (isRunningSeed(member)) { result.add(member); } } if (log.isTraceEnabled()) log.trace("Viable running seeds in Cluster {}: {}", new Object[] {result}); return result; } public boolean isViableSeed(Entity member) { // TODO would be good to reuse the better logic in ServiceFailureDetector // (e.g. if that didn't just emit a notification but set a sensor as well?) boolean managed = Entities.isManaged(member); String hostname = member.getAttribute(Attributes.HOSTNAME); boolean serviceUp = Boolean.TRUE.equals(member.getAttribute(Attributes.SERVICE_UP)); Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL); boolean hasFailed = !managed || (serviceState == Lifecycle.ON_FIRE) || (serviceState == Lifecycle.RUNNING && !serviceUp) || (serviceState == Lifecycle.STOPPED); boolean result = (hostname != null && !hasFailed); if (log.isTraceEnabled()) log.trace("Node {} in Cluster {}: viableSeed={}; hostname={}; serviceUp={}; serviceState={}; hasFailed={}", new Object[] {member, this, result, hostname, serviceUp, serviceState, hasFailed}); return result; } public boolean isRunningSeed(Entity member) { boolean viableSeed = isViableSeed(member); boolean serviceUp = Boolean.TRUE.equals(member.getAttribute(Attributes.SERVICE_UP)); Lifecycle serviceState = member.getAttribute(Attributes.SERVICE_STATE_ACTUAL); boolean result = viableSeed && serviceUp && serviceState == Lifecycle.RUNNING; if (log.isTraceEnabled()) log.trace("Node {} in Cluster {}: runningSeed={}; viableSeed={}; serviceUp={}; serviceState={}", new Object[] {member, this, result, viableSeed, serviceUp, serviceState}); return result; } } @Override public String executeScript(String commands) { Entity someChild = Iterables.getFirst(getMembers(), null); if (someChild==null) throw new IllegalStateException("No Cassandra nodes available"); // FIXME cross-etntity method-style calls such as below do not set up a queueing context (DynamicSequentialTask) // return ((CassandraNode)someChild).executeScript(commands); return Entities.invokeEffector(this, someChild, CassandraNode.EXECUTE_SCRIPT, MutableMap.of("commands", commands)).getUnchecked(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy