private Integer hashCodeIfResolved(ConfigKey<?>... keys) { int hash = 0; for (ConfigKey<?> k : keys) { Maybe<?> value = ((ConfigurationSupportInternal) getEntity().config()).getNonBlocking(k); if (value.isPresent()) { hash = hash * 31 + (value.get() == null ? 0 : value.get().hashCode()); } } return hash; }
protected Location getLocation(@Nullable Collection<? extends Location> locations) { if (locations == null || locations.isEmpty()) locations = entity().getLocations(); if (locations.isEmpty()) { MachineProvisioningLocation<?> provisioner = entity().getAttribute(SoftwareProcess.PROVISIONING_LOCATION); if (provisioner != null) locations = Arrays.<Location>asList(provisioner); } locations = Locations.getLocationsCheckingAncestors(locations, entity()); Maybe<MachineLocation> ml = Locations.findUniqueMachineLocation(locations); if (ml.isPresent()) return ml.get(); if (locations.isEmpty()) throw new IllegalArgumentException("No locations specified when starting " + entity()); if (locations.size() != 1 || Iterables.getOnlyElement(locations) == null) throw new IllegalArgumentException( "Ambiguous locations detected when starting " + entity() + ": " + locations); return Iterables.getOnlyElement(locations); }
@Override public String getSeeds() { Set<Entity> seeds = getConfig(CassandraNode.INITIAL_SEEDS); if (seeds == null) { log.warn( "No seeds available when requested for " + this, new Throwable("source of no Cassandra seeds when requested")); return null; } String snitchName = getConfig(CassandraNode.ENDPOINT_SNITCH_NAME); MutableSet<String> seedsHostnames = MutableSet.of(); for (Entity entity : seeds) { // tried removing ourselves if there are other nodes, but that is a BAD idea! // blows up with a "java.lang.RuntimeException: No other nodes seen!" if (snitchName.equals("Ec2MultiRegionSnitch") || snitchName.contains("MultiCloudSnitch")) { // http://www.datastax.com/documentation/cassandra/2.0/mobile/cassandra/architecture/architectureSnitchEC2MultiRegion_c.html // says the seeds should be public IPs. seedsHostnames.add(entity.getAttribute(CassandraNode.ADDRESS)); } else { String sensorName = getConfig(BROADCAST_ADDRESS_SENSOR); if (Strings.isNonBlank(sensorName)) { seedsHostnames.add(entity.getAttribute(Sensors.newStringSensor(sensorName))); } else { Maybe<String> optionalSeedHostname = Machines.findSubnetOrPublicHostname(entity); if (optionalSeedHostname.isPresent()) { String seedHostname = optionalSeedHostname.get(); seedsHostnames.add(seedHostname); } else { log.warn( "In node {}, seed hostname missing for {}; not including in seeds list", this, entity); } } } } String result = Strings.join(seedsHostnames, ","); log.info("Seeds for {}: {}", this, result); return result; }
protected void configureResolver(Entity entity) { Maybe<SshMachineLocation> machine = Machines.findUniqueSshMachineLocation(entity.getLocations()); if (machine.isPresent()) { if (getConfig(REPLACE_RESOLV_CONF)) { machine.get().copyTo(new StringReader(getConfig(RESOLV_CONF_TEMPLATE)), "/etc/resolv.conf"); } else { appendTemplate( getConfig(INTERFACE_CONFIG_TEMPLATE), "/etc/sysconfig/network-scripts/ifcfg-eth0", machine.get()); machine .get() .execScript( "reload network", ImmutableList.of(BashCommands.sudo("service network reload"))); } LOG.info("configured resolver on {}", machine); } else { LOG.debug("{} can't configure resolver at {}: no SshMachineLocation", this, entity); } }
public void update() { Lifecycle serverState = getAttribute(Attributes.SERVICE_STATE_ACTUAL); if (Lifecycle.STOPPED.equals(serverState) || Lifecycle.STOPPING.equals(serverState) || Lifecycle.DESTROYED.equals(serverState) || !getAttribute(Attributes.SERVICE_UP)) { LOG.debug( "Skipped update of {} when service state is {} and running is {}", new Object[] { this, getAttribute(Attributes.SERVICE_STATE_ACTUAL), getAttribute(SERVICE_UP) }); return; } synchronized (this) { Iterable<Entity> availableEntities = FluentIterable.from(getEntities().getMembers()) .filter(new HasHostnameAndValidLifecycle()); LOG.debug("{} updating with entities: {}", this, Iterables.toString(availableEntities)); ImmutableListMultimap<String, Entity> hostnameToEntity = Multimaps.index(availableEntities, new HostnameTransformer()); Map<String, String> octetToName = Maps.newHashMap(); BiMap<String, String> ipToARecord = HashBiMap.create(); Multimap<String, String> aRecordToCnames = MultimapBuilder.hashKeys().hashSetValues().build(); Multimap<String, String> ipToAllNames = MultimapBuilder.hashKeys().hashSetValues().build(); for (Map.Entry<String, Entity> e : hostnameToEntity.entries()) { String domainName = e.getKey(); Maybe<SshMachineLocation> location = Machines.findUniqueSshMachineLocation(e.getValue().getLocations()); if (!location.isPresent()) { LOG.debug( "Member {} of {} does not have an SSH location so will not be configured", e.getValue(), this); continue; } else if (ipToARecord.inverse().containsKey(domainName)) { continue; } String address = location.get().getAddress().getHostAddress(); ipToAllNames.put(address, domainName); if (!ipToARecord.containsKey(address)) { ipToARecord.put(address, domainName); if (getReverseLookupNetwork().contains(new Cidr(address + "/32"))) { String octet = Iterables.get(Splitter.on('.').split(address), 3); if (!octetToName.containsKey(octet)) octetToName.put(octet, domainName); } } else { aRecordToCnames.put(ipToARecord.get(address), domainName); } } setAttribute(A_RECORDS, ImmutableMap.copyOf(ipToARecord.inverse())); setAttribute(PTR_RECORDS, ImmutableMap.copyOf(octetToName)); setAttribute(CNAME_RECORDS, Multimaps.unmodifiableMultimap(aRecordToCnames)); setAttribute(ADDRESS_MAPPINGS, Multimaps.unmodifiableMultimap(ipToAllNames)); // Update Bind configuration files and restart the service getDriver().updateBindConfiguration(); } }
public static HostAndPort getBrooklynAccessibleAddress(Entity entity, int port) { String host; // look up port forwarding PortForwardManager pfw = entity.getConfig(PORT_FORWARDING_MANAGER); if (pfw != null) { Collection<Location> ll = entity.getLocations(); synchronized (BrooklynAccessUtils.class) { // TODO finer-grained synchronization for (MachineLocation machine : Iterables.filter(ll, MachineLocation.class)) { HostAndPort hp = pfw.lookup(machine, port); if (hp != null) { log.debug( "BrooklynAccessUtils found port-forwarded address {} for entity {}, port {}, using machine {}", new Object[] {hp, entity, port, machine}); return hp; } } Maybe<SupportsPortForwarding> supportPortForwardingLoc = Machines.findUniqueElement(ll, SupportsPortForwarding.class); if (supportPortForwardingLoc.isPresent()) { Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR); SupportsPortForwarding loc = supportPortForwardingLoc.get(); if (source != null) { log.debug( "BrooklynAccessUtils requesting new port-forwarding rule to access " + port + " on " + entity + " (at " + loc + ", enabled for " + source + ")"); // TODO discuss, is this the best way to do it // (will probably _create_ the port forwarding rule!) HostAndPort hp = loc.getSocketEndpointFor(source, port); if (hp != null) { log.debug( "BrooklynAccessUtils created port-forwarded address {} for entity {}, port {}, using {}", new Object[] {hp, entity, port, loc}); return hp; } } else { log.warn( "No " + MANAGEMENT_ACCESS_CIDR.getName() + " configured for " + entity + ", so cannot forward " + "port " + port + " " + "even though " + PORT_FORWARDING_MANAGER.getName() + " was supplied, and " + "have location supporting port forwarding " + loc); } } } } host = entity.getAttribute(Attributes.HOSTNAME); if (host != null) return HostAndPort.fromParts(host, port); throw new IllegalStateException( "Cannot find way to access port " + port + " on " + entity + " from Brooklyn (no host.name)"); }
protected void doStop(ConfigBag parameters, Callable<StopMachineDetails<Integer>> stopTask) { preStopConfirmCustom(); log.info("Stopping {} in {}", entity(), entity().getLocations()); StopMode stopMachineMode = getStopMachineMode(parameters); StopMode stopProcessMode = parameters.get(StopSoftwareParameters.STOP_PROCESS_MODE); DynamicTasks.queue("pre-stop", new PreStopCustomTask()); // BROOKLYN-263: // With this change the stop effector will wait for Location to provision so it can terminate // the machine, if a provisioning request is in-progress. // // The ProvisionMachineTask stores transient internal state in PROVISIONING_TASK_STATE and // PROVISIONED_MACHINE: it records when the provisioning is running and when done; and it // records the final machine. We record the machine in the internal sensor (rather than // just relying on getLocations) because the latter is set much later in the start() // process. // // This code is a big improvement (previously there was a several-minute window in some // clouds where a call to stop() would leave the machine running). // // However, there are still races. If the start() code has not yet reached the call to // location.obtain() then we won't wait, and the start() call won't know to abort. It's // fiddly to get that right, because we need to cope with restart() - so we mustn't leave // any state behind that will interfere with subsequent sequential calls to start(). // There is some attempt to handle it by ProvisionMachineTask checking if the expectedState // is stopping/stopped. Maybe<MachineLocation> machine = Machines.findUniqueMachineLocation(entity().getLocations()); ProvisioningTaskState provisioningState = entity().sensors().get(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE); if (machine.isAbsent() && provisioningState == ProvisioningTaskState.RUNNING) { Duration maxWait = entity().config().get(STOP_WAIT_PROVISIONING_TIMEOUT); log.info( "When stopping {}, waiting for up to {} for the machine to finish provisioning, before terminating it", entity(), maxWait); boolean success = Repeater.create("Wait for a machine to appear") .until( new Callable<Boolean>() { @Override public Boolean call() throws Exception { ProvisioningTaskState state = entity() .sensors() .get(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE); return (state != ProvisioningTaskState.RUNNING); } }) .backoffTo(Duration.FIVE_SECONDS) .limitTimeTo(maxWait) .run(); if (!success) { log.warn( "When stopping {}, timed out after {} waiting for the machine to finish provisioning - machine may we left running", entity(), maxWait); } machine = Maybe.ofDisallowingNull(entity().sensors().get(INTERNAL_PROVISIONED_MACHINE)); } entity().sensors().remove(AttributesInternal.INTERNAL_PROVISIONING_TASK_STATE); entity().sensors().remove(INTERNAL_PROVISIONED_MACHINE); Task<List<?>> stoppingProcess = null; if (canStop(stopProcessMode, entity())) { stoppingProcess = Tasks.parallel( "stopping", Tasks.create("stopping (process)", new StopProcessesAtMachineTask()), Tasks.create("stopping (feeds)", new StopFeedsAtMachineTask())); DynamicTasks.queue(stoppingProcess); } Task<StopMachineDetails<Integer>> stoppingMachine = null; if (canStop(stopMachineMode, machine.isAbsent())) { // Release this machine (even if error trying to stop process - we rethrow that after) Map<String, Object> stopMachineFlags = MutableMap.of(); if (Entitlements.getEntitlementContext() != null) { stopMachineFlags.put( "tags", MutableSet.of( BrooklynTaskTags.tagForEntitlement(Entitlements.getEntitlementContext()))); } Task<StopMachineDetails<Integer>> stopMachineTask = Tasks.<StopMachineDetails<Integer>>builder() .displayName("stopping (machine)") .body(stopTask) .flags(stopMachineFlags) .build(); stoppingMachine = DynamicTasks.queue(stopMachineTask); DynamicTasks.drain(entity().getConfig(STOP_PROCESS_TIMEOUT), false); // shutdown the machine if stopping process fails or takes too long synchronized (stoppingMachine) { // task also used as mutex by DST when it submits it; ensure it only submits once! if (!stoppingMachine.isSubmitted()) { // force the stoppingMachine task to run by submitting it here StringBuilder msg = new StringBuilder("Submitting machine stop early in background for ") .append(entity()); if (stoppingProcess == null) { msg.append(". Process stop skipped, pre-stop not finished?"); } else { msg.append(" because process stop has ") .append((stoppingProcess.isDone() ? "finished abnormally" : "not finished")); } log.warn(msg.toString()); Entities.submit(entity(), stoppingMachine); } } } try { // This maintains previous behaviour of silently squashing any errors on the stoppingProcess // task if the // stoppingMachine exits with a nonzero value boolean checkStopProcesses = (stoppingProcess != null && (stoppingMachine == null || stoppingMachine.get().value == 0)); if (checkStopProcesses) { // TODO we should test for destruction above, not merely successful "stop", as things like // localhost and ssh won't be destroyed DynamicTasks.waitForLast(); if (machine.isPresent()) { // throw early errors *only if* there is a machine and we have not destroyed it stoppingProcess.get(); } } } catch (Throwable e) { ServiceStateLogic.setExpectedState(entity(), Lifecycle.ON_FIRE); Exceptions.propagate(e); } entity().sensors().set(SoftwareProcess.SERVICE_UP, false); ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPED); DynamicTasks.queue("post-stop", new PostStopCustomTask()); if (log.isDebugEnabled()) log.debug("Stopped software process entity " + entity()); }
public void run() { log.info("Starting {} on machine {}", entity(), machine); Collection<Location> oldLocs = entity().getLocations(); if (!oldLocs.isEmpty()) { List<MachineLocation> oldSshLocs = ImmutableList.copyOf(Iterables.filter(oldLocs, MachineLocation.class)); if (!oldSshLocs.isEmpty()) { // check if existing locations are compatible log.debug( "Entity " + entity() + " had machine locations " + oldSshLocs + " when starting at " + machine + "; checking if they are compatible"); for (MachineLocation oldLoc : oldSshLocs) { // machines are deemed compatible if hostname and address are the same, or they are // localhost // this allows a machine create by jclouds to then be defined with an ip-based spec if (!"localhost".equals(machine.getConfig(AbstractLocation.ORIGINAL_SPEC))) { checkLocationParametersCompatible( machine, oldLoc, "hostname", oldLoc.getAddress().getHostName(), machine.getAddress().getHostName()); checkLocationParametersCompatible( machine, oldLoc, "address", oldLoc.getAddress().getHostAddress(), machine.getAddress().getHostAddress()); } } log.debug( "Entity " + entity() + " old machine locations " + oldSshLocs + " were compatible, removing them to start at " + machine); entity().removeLocations(oldSshLocs); } } entity().addLocations(ImmutableList.of((Location) machine)); // elsewhere we rely on (public) hostname being set _after_ subnet_hostname // (to prevent the tiny possibility of races resulting in hostname being returned // simply because subnet is still being looked up) Maybe<String> lh = Machines.getSubnetHostname(machine); Maybe<String> la = Machines.getSubnetIp(machine); if (lh.isPresent()) entity().sensors().set(Attributes.SUBNET_HOSTNAME, lh.get()); if (la.isPresent()) entity().sensors().set(Attributes.SUBNET_ADDRESS, la.get()); entity().sensors().set(Attributes.HOSTNAME, machine.getAddress().getHostName()); entity().sensors().set(Attributes.ADDRESS, machine.getAddress().getHostAddress()); if (machine instanceof SshMachineLocation) { SshMachineLocation sshMachine = (SshMachineLocation) machine; UserAndHostAndPort sshAddress = UserAndHostAndPort.fromParts( sshMachine.getUser(), sshMachine.getAddress().getHostName(), sshMachine.getPort()); // FIXME: Who or what is SSH_ADDRESS intended for? It's not necessarily the address that // the SshMachineLocation is using for ssh connections (because it accepts SSH_HOST as an // override). entity().sensors().set(Attributes.SSH_ADDRESS, sshAddress); } if (Boolean.TRUE.equals(entity().getConfig(SoftwareProcess.OPEN_IPTABLES))) { if (machine instanceof SshMachineLocation) { @SuppressWarnings("unchecked") Iterable<Integer> inboundPorts = (Iterable<Integer>) machine.config().get(CloudLocationConfig.INBOUND_PORTS); machineInitTasks.openIptablesAsync(inboundPorts, (SshMachineLocation) machine); } else { log.warn("Ignoring flag OPEN_IPTABLES on non-ssh location {}", machine); } } if (Boolean.TRUE.equals(entity().getConfig(SoftwareProcess.STOP_IPTABLES))) { if (machine instanceof SshMachineLocation) { machineInitTasks.stopIptablesAsync((SshMachineLocation) machine); } else { log.warn("Ignoring flag STOP_IPTABLES on non-ssh location {}", machine); } } if (Boolean.TRUE.equals(entity().getConfig(SoftwareProcess.DONT_REQUIRE_TTY_FOR_SUDO))) { if (machine instanceof SshMachineLocation) { machineInitTasks.dontRequireTtyForSudoAsync((SshMachineLocation) machine); } else { log.warn("Ignoring flag DONT_REQUIRE_TTY_FOR_SUDO on non-ssh location {}", machine); } } resolveOnBoxDir(entity(), machine); preStartCustom(machine); }
@SuppressWarnings({"rawtypes", "unchecked"}) @Override public V call() { T value = source.getAttribute(sensor); // return immediately if either the ready predicate or the abort conditions hold if (ready(value)) return postProcess(value); final List<Exception> abortionExceptions = Lists.newCopyOnWriteArrayList(); long start = System.currentTimeMillis(); for (AttributeAndSensorCondition abortCondition : abortSensorConditions) { Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor); if (abortCondition.predicate.apply(abortValue)) { abortionExceptions.add( new Exception( "Abort due to " + abortCondition.source + " -> " + abortCondition.sensor)); } } if (abortionExceptions.size() > 0) { throw new CompoundRuntimeException( "Aborted waiting for ready from " + source + " " + sensor, abortionExceptions); } TaskInternal<?> current = (TaskInternal<?>) Tasks.current(); if (current == null) throw new IllegalStateException("Should only be invoked in a running task"); Entity entity = BrooklynTaskTags.getTargetOrContextEntity(current); if (entity == null) throw new IllegalStateException( "Should only be invoked in a running task with an entity tag; " + current + " has no entity tag (" + current.getStatusDetail(false) + ")"); final LinkedList<T> publishedValues = new LinkedList<T>(); final Semaphore semaphore = new Semaphore(0); // could use Exchanger SubscriptionHandle subscription = null; List<SubscriptionHandle> abortSubscriptions = Lists.newArrayList(); try { subscription = entity .subscriptions() .subscribe( source, sensor, new SensorEventListener<T>() { @Override public void onEvent(SensorEvent<T> event) { synchronized (publishedValues) { publishedValues.add(event.getValue()); } semaphore.release(); } }); for (final AttributeAndSensorCondition abortCondition : abortSensorConditions) { abortSubscriptions.add( entity .subscriptions() .subscribe( abortCondition.source, abortCondition.sensor, new SensorEventListener<Object>() { @Override public void onEvent(SensorEvent<Object> event) { if (abortCondition.predicate.apply(event.getValue())) { abortionExceptions.add( new Exception( "Abort due to " + abortCondition.source + " -> " + abortCondition.sensor)); semaphore.release(); } } })); Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor); if (abortCondition.predicate.apply(abortValue)) { abortionExceptions.add( new Exception( "Abort due to " + abortCondition.source + " -> " + abortCondition.sensor)); } } if (abortionExceptions.size() > 0) { throw new CompoundRuntimeException( "Aborted waiting for ready from " + source + " " + sensor, abortionExceptions); } CountdownTimer timer = timeout != null ? timeout.countdownTimer() : null; Duration maxPeriod = ValueResolver.PRETTY_QUICK_WAIT; Duration nextPeriod = ValueResolver.REAL_QUICK_PERIOD; while (true) { // check the source on initial run (could be done outside the loop) // and also (optionally) on each iteration in case it is more recent value = source.getAttribute(sensor); if (ready(value)) break; if (timer != null) { if (timer.getDurationRemaining().isShorterThan(nextPeriod)) { nextPeriod = timer.getDurationRemaining(); } if (timer.isExpired()) { if (onTimeout.isPresent()) return onTimeout.get(); throw new RuntimeTimeoutException("Unsatisfied after " + Duration.sinceUtc(start)); } } String prevBlockingDetails = current.setBlockingDetails(blockingDetails); try { if (semaphore.tryAcquire(nextPeriod.toMilliseconds(), TimeUnit.MILLISECONDS)) { // immediately release so we are available for the next check semaphore.release(); // if other permits have been made available (e.g. multiple notifications) drain them // all as no point running multiple times semaphore.drainPermits(); } } finally { current.setBlockingDetails(prevBlockingDetails); } // check any subscribed values which have come in first while (true) { synchronized (publishedValues) { if (publishedValues.isEmpty()) break; value = publishedValues.pop(); } if (ready(value)) break; } // if unmanaged then ignore the other abort conditions if (!ignoreUnmanaged && Entities.isNoLongerManaged(entity)) { if (onUnmanaged.isPresent()) return onUnmanaged.get(); throw new NotManagedException(entity); } if (abortionExceptions.size() > 0) { throw new CompoundRuntimeException( "Aborted waiting for ready from " + source + " " + sensor, abortionExceptions); } nextPeriod = nextPeriod.times(2).upperBound(maxPeriod); } if (LOG.isDebugEnabled()) LOG.debug("Attribute-ready for {} in entity {}", sensor, source); return postProcess(value); } catch (InterruptedException e) { throw Exceptions.propagate(e); } finally { if (subscription != null) { entity.subscriptions().unsubscribe(subscription); } for (SubscriptionHandle handle : abortSubscriptions) { entity.subscriptions().unsubscribe(handle); } } }
@Override @Deprecated public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) { Maybe<Object> result = delegate.getConfigRaw(key, includeInherited); return (result.isPresent()) ? Maybe.of(transform(key, result.get())) : Maybe.absent(); }