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

org.apache.brooklyn.entity.chef.ChefAttributeFeed 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.chef;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.feed.AbstractFeed;
import org.apache.brooklyn.core.feed.PollHandler;
import org.apache.brooklyn.core.feed.Poller;
import org.apache.brooklyn.feed.ssh.SshPollValue;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
 * A sensor feed that retrieves attributes from Chef server and converts selected attributes to sensors.
 *
 * 

To use this feed, you must provide the entity, the name of the node as it is known to Chef, and a collection of attribute * sensors. The attribute sensors must follow the naming convention of starting with the string chef.attribute. * followed by a period-separated path through the Chef attribute hierarchy. For example, an attribute sensor named * chef.attribute.sql_server.instance_name would cause the feed to search for a Chef attribute called * sql_server, and within that an attribute instance_name, and set the sensor to the value of this * attribute.

* *

This feed uses the knife tool to query all the attributes on a named node. It then iterates over the configured * list of attribute sensors, using the sensor name to locate an equivalent Chef attribute. The sensor is then set to the value * of the Chef attribute.

* *

Example:

* * {@code * @Override * protected void connectSensors() { * nodeAttributesFeed = ChefAttributeFeed.newFeed(this, nodeName, new AttributeSensor[]{ * SqlServerNode.CHEF_ATTRIBUTE_NODE_NAME, * SqlServerNode.CHEF_ATTRIBUTE_SQL_SERVER_INSTANCE_NAME, * SqlServerNode.CHEF_ATTRIBUTE_SQL_SERVER_PORT, * SqlServerNode.CHEF_ATTRIBUTE_SQL_SERVER_SA_PASSWORD * }); * } * } * * @since 0.6.0 * @author richardcloudsoft */ public class ChefAttributeFeed extends AbstractFeed { private static final Logger log = LoggerFactory.getLogger(ChefAttributeFeed.class); /** * Prefix for attribute sensor names. */ public static final String CHEF_ATTRIBUTE_PREFIX = "chef.attribute."; @SuppressWarnings("serial") public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( new TypeToken>>() {}, "polls"); public static final ConfigKey NODE_NAME = ConfigKeys.newStringConfigKey("nodeName"); public static Builder builder() { return new Builder(); } @SuppressWarnings("rawtypes") public static class Builder { private Entity entity; private boolean onlyIfServiceUp = false; private String nodeName; private Set polls = Sets.newLinkedHashSet(); private Duration period = Duration.of(30, TimeUnit.SECONDS); private String uniqueTag; private volatile boolean built; public Builder entity(Entity val) { this.entity = checkNotNull(val, "entity"); return this; } public Builder onlyIfServiceUp() { return onlyIfServiceUp(true); } public Builder onlyIfServiceUp(boolean onlyIfServiceUp) { this.onlyIfServiceUp = onlyIfServiceUp; return this; } public Builder nodeName(String nodeName) { this.nodeName = checkNotNull(nodeName, "nodeName"); return this; } public Builder addSensor(ChefAttributePollConfig config) { polls.add(config); return this; } @SuppressWarnings("unchecked") public Builder addSensor(String chefAttributePath, AttributeSensor sensor) { return addSensor(new ChefAttributePollConfig(sensor).chefAttributePath(chefAttributePath)); } public Builder addSensors(Map sensors) { for (Map.Entry entry : sensors.entrySet()) { addSensor(entry.getKey(), entry.getValue()); } return this; } public Builder addSensors(AttributeSensor[] sensors) { return addSensors(Arrays.asList(checkNotNull(sensors, "sensors"))); } public Builder addSensors(Iterable sensors) { for(AttributeSensor sensor : checkNotNull(sensors, "sensors")) { checkNotNull(sensor, "sensors collection contains a null value"); checkArgument(sensor.getName().startsWith(CHEF_ATTRIBUTE_PREFIX), "sensor name must be prefixed "+CHEF_ATTRIBUTE_PREFIX+" for autodetection to work"); addSensor(sensor.getName().substring(CHEF_ATTRIBUTE_PREFIX.length()), sensor); } return this; } public Builder period(Duration period) { this.period = period; return this; } public Builder period(long millis) { return period(Duration.of(millis, TimeUnit.MILLISECONDS)); } public Builder period(long val, TimeUnit units) { return period(Duration.of(val, units)); } public Builder uniqueTag(String uniqueTag) { this.uniqueTag = uniqueTag; return this; } public ChefAttributeFeed build() { built = true; ChefAttributeFeed result = new ChefAttributeFeed(this); result.setEntity(checkNotNull((EntityLocal)entity, "entity")); result.start(); return result; } @Override protected void finalize() { if (!built) log.warn("SshFeed.Builder created, but build() never called"); } } private KnifeTaskFactory knifeTaskFactory; /** * For rebind; do not call directly; use builder */ public ChefAttributeFeed() { } protected ChefAttributeFeed(Builder builder) { setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp); setConfig(NODE_NAME, checkNotNull(builder.nodeName, "builder.nodeName")); Set> polls = Sets.newLinkedHashSet(); for (ChefAttributePollConfig config : builder.polls) { if (!config.isEnabled()) continue; @SuppressWarnings({ "unchecked", "rawtypes" }) ChefAttributePollConfig configCopy = new ChefAttributePollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period); polls.add(configCopy); } setConfig(POLLS, polls); initUniqueTag(builder.uniqueTag, polls); } @Override protected void preStart() { final String nodeName = getConfig(NODE_NAME); final Set> polls = getConfig(POLLS); long minPeriod = Integer.MAX_VALUE; for (ChefAttributePollConfig config : polls) { minPeriod = Math.min(minPeriod, config.getPeriod()); } knifeTaskFactory = new KnifeNodeAttributeQueryTaskFactory(nodeName); final Callable getAttributesFromKnife = new Callable() { @Override public SshPollValue call() throws Exception { ProcessTaskWrapper taskWrapper = knifeTaskFactory.newTask(); final ExecutionContext executionContext = ((EntityInternal) entity).getExecutionContext(); log.debug("START: Running knife to query attributes of Chef node {}", nodeName); executionContext.submit(taskWrapper); taskWrapper.block(); log.debug("DONE: Running knife to query attributes of Chef node {}", nodeName); return new SshPollValue(null, taskWrapper.getExitCode(), taskWrapper.getStdout(), taskWrapper.getStderr()); } }; getPoller().scheduleAtFixedRate( new CallInEntityExecutionContext(entity, getAttributesFromKnife), new SendChefAttributesToSensors(entity, polls), minPeriod); } @Override @SuppressWarnings("unchecked") protected Poller getPoller() { return (Poller) super.getPoller(); } /** * An implementation of {@link KnifeTaskFactory} that queries for the attributes of a node. */ private static class KnifeNodeAttributeQueryTaskFactory extends KnifeTaskFactory { private final String nodeName; public KnifeNodeAttributeQueryTaskFactory(String nodeName) { super("retrieve attributes of node " + nodeName); this.nodeName = nodeName; } @Override protected List initialKnifeParameters() { return ImmutableList.of("node", "show", "-l", nodeName, "--format", "json"); } } /** * A {@link Callable} that wraps another {@link Callable}, where the inner {@link Callable} is executed in the context of a * specific entity. * * @param The type of the {@link Callable}. */ private static class CallInEntityExecutionContext implements Callable { private final Callable job; private Entity entity; private CallInEntityExecutionContext(Entity entity, Callable job) { this.job = job; this.entity = entity; } @Override public T call() throws Exception { final ExecutionContext executionContext = ((EntityInternal) entity).getExecutionContext(); return executionContext.submit(Maps.newHashMap(), job).get(); } } /** * A poll handler that takes the result of the knife invocation and sets the appropriate sensors. */ private static class SendChefAttributesToSensors implements PollHandler { private static final Iterable PREFIXES = ImmutableList.of("", "automatic", "force_override", "override", "normal", "force_default", "default"); private static final Splitter SPLITTER = Splitter.on('.'); private final Entity entity; private final Map> chefAttributeSensors; public SendChefAttributesToSensors(Entity entity, Set> polls) { this.entity = entity; chefAttributeSensors = Maps.newLinkedHashMap(); for (ChefAttributePollConfig config : polls) { chefAttributeSensors.put(config.getChefAttributePath(), config.getSensor()); } } @Override public boolean checkSuccess(SshPollValue val) { if (val.getExitStatus() != 0) return false; String stderr = val.getStderr(); if (stderr == null || stderr.length() != 0) return false; String out = val.getStdout(); if (out == null || out.length() == 0) return false; if (!out.contains("{")) return false; return true; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void onSuccess(SshPollValue val) { String stdout = val.getStdout(); int jsonStarts = stdout.indexOf('{'); if (jsonStarts > 0) stdout = stdout.substring(jsonStarts); JsonElement jsonElement = new Gson().fromJson(stdout, JsonElement.class); for (Map.Entry> attribute : chefAttributeSensors.entrySet()) { String chefAttributeName = attribute.getKey(); AttributeSensor sensor = attribute.getValue(); log.trace("Finding value for attribute sensor " + sensor.getName()); Iterable path = SPLITTER.split(chefAttributeName); JsonElement elementForSensor = null; for(String prefix : PREFIXES) { Iterable prefixedPath = !Strings.isNullOrEmpty(prefix) ? Iterables.concat(ImmutableList.of(prefix), path) : path; try { elementForSensor = getElementByPath(jsonElement.getAsJsonObject(), prefixedPath); } catch(IllegalArgumentException e) { log.error("Entity {}: bad Chef attribute {} for sensor {}: {}", new Object[]{ entity.getDisplayName(), Joiner.on('.').join(prefixedPath), sensor.getName(), e.getMessage()}); throw Throwables.propagate(e); } if (elementForSensor != null) { log.debug("Entity {}: apply Chef attribute {} to sensor {} with value {}", new Object[]{ entity.getDisplayName(), Joiner.on('.').join(prefixedPath), sensor.getName(), elementForSensor.getAsString()}); break; } } if (elementForSensor != null) { entity.sensors().set((AttributeSensor)sensor, TypeCoercions.coerce(elementForSensor.getAsString(), sensor.getTypeToken())); } else { log.debug("Entity {}: no Chef attribute matching {}; setting sensor {} to null", new Object[]{ entity.getDisplayName(), chefAttributeName, sensor.getName()}); entity.sensors().set(sensor, null); } } } private JsonElement getElementByPath(JsonElement element, Iterable path) { if (Iterables.isEmpty(path)) { return element; } else { String head = Iterables.getFirst(path, null); Preconditions.checkArgument(!Strings.isNullOrEmpty(head), "path must not contain empty or null elements"); Iterable tail = Iterables.skip(path, 1); JsonElement child = ((JsonObject) element).get(head); return child != null ? getElementByPath(child, tail) : null; } } @Override public void onFailure(SshPollValue val) { log.error("Chef attribute query did not respond as expected. exitcode={} stdout={} stderr={}", new Object[]{val.getExitStatus(), val.getStdout(), val.getStderr()}); for (AttributeSensor attribute : chefAttributeSensors.values()) { if (!attribute.getName().startsWith(CHEF_ATTRIBUTE_PREFIX)) continue; entity.sensors().set(attribute, null); } } @Override public void onException(Exception exception) { log.error("Detected exception while retrieving Chef attributes from entity " + entity.getDisplayName(), exception); for (AttributeSensor attribute : chefAttributeSensors.values()) { if (!attribute.getName().startsWith(CHEF_ATTRIBUTE_PREFIX)) continue; entity.sensors().set(attribute, null); } } @Override public String toString() { return super.toString()+"["+getDescription()+"]"; } @Override public String getDescription() { return ""+chefAttributeSensors; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy