http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java new file mode 100644 index 0000000..f572b1f --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefConfigs.java @@ -0,0 +1,102 @@ +/* + * 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 java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.MapConfigKey.MapModifications; +import org.apache.brooklyn.core.config.SetConfigKey.SetModifications; +import org.apache.brooklyn.entity.core.EntityInternal; +import org.apache.brooklyn.util.git.GithubUrls; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +/** Conveniences for configuring brooklyn Chef entities + * @since 0.6.0 */ +@Beta +public class ChefConfigs { + + public static void addToLaunchRunList(EntitySpec<?> entity, String ...recipes) { + for (String recipe: recipes) + entity.configure(ChefConfig.CHEF_LAUNCH_RUN_LIST, SetModifications.addItem(recipe)); + } + + public static void addToLaunchRunList(EntityInternal entity, String ...recipes) { + for (String recipe: recipes) + entity.setConfig(ChefConfig.CHEF_LAUNCH_RUN_LIST, SetModifications.addItem(recipe)); + } + + public static void addToCookbooksFromGithub(EntitySpec<?> entity, String ...cookbookNames) { + for (String cookbookName: cookbookNames) + addToCookbooksFromGithub(entity, cookbookName, getGithubOpscodeRepo(cookbookName)); + } + + public static void addToCookbooksFromGithub(EntityInternal entity, String ...cookbookNames) { + for (String cookbookName: cookbookNames) + addToCookbooksFromGithub(entity, cookbookName, getGithubOpscodeRepo(cookbookName)); + } + + public static String getGithubOpscodeRepo(String cookbookName) { + return getGithubOpscodeRepo(cookbookName, "master"); + } + public static String getGithubOpscodeRepo(String cookbookName, String tag) { + return GithubUrls.tgz("opscode-cookbooks", cookbookName, tag); + } + + public static void addToCookbooksFromGithub(EntitySpec<?> entity, String cookbookName, String cookbookUrl) { + entity.configure(ChefConfig.CHEF_COOKBOOK_URLS.subKey(cookbookName), cookbookUrl); + } + + public static void addToCookbooksFromGithub(EntityInternal entity, String cookbookName, String cookbookUrl) { + entity.setConfig(ChefConfig.CHEF_COOKBOOK_URLS.subKey(cookbookName), cookbookUrl); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends Object,? extends Object> attributesMap) { + entity.configure(ChefConfig.CHEF_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void addLaunchAttributes(EntityInternal entity, Map<? extends Object,? extends Object> attributesMap) { + entity.setConfig(ChefConfig.CHEF_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); + } + + /** replaces the attributes underneath the rootAttribute parameter with the given value; + * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ + public static void setLaunchAttribute(EntitySpec<?> entity, String rootAttribute, Object value) { + entity.configure(ChefConfig.CHEF_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); + } + + /** replaces the attributes underneath the rootAttribute parameter with the given value; + * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ + public static void setLaunchAttribute(EntityInternal entity, String rootAttribute, Object value) { + entity.setConfig(ChefConfig.CHEF_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); + } + + public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) { + return Preconditions.checkNotNull( + Preconditions.checkNotNull(entity, "Entity must be supplied").getConfig(key), + "Key "+key+" is required on "+entity); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java new file mode 100644 index 0000000..1b1f866 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntity.java @@ -0,0 +1,26 @@ +/* + * 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 org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; + +@ImplementedBy(ChefEntityImpl.class) +public interface ChefEntity extends SoftwareProcess, ChefConfig { +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java new file mode 100644 index 0000000..911b546 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefEntityImpl.java @@ -0,0 +1,38 @@ +/* + * 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 org.apache.brooklyn.entity.stock.EffectorStartableImpl; +import org.apache.brooklyn.util.text.Strings; + +public class ChefEntityImpl extends EffectorStartableImpl implements ChefEntity { + + public void init() { + String primaryName = getConfig(CHEF_COOKBOOK_PRIMARY_NAME); + if (!Strings.isBlank(primaryName)) setDefaultDisplayName(primaryName+" (chef)"); + + super.init(); + new ChefLifecycleEffectorTasks().attachLifecycleEffectors(this); + } + + @Override + public void populateServiceNotUpDiagnostics() { + // TODO no-op currently; should check ssh'able etc + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java new file mode 100644 index 0000000..e4cbf1c --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefLifecycleEffectorTasks.java @@ -0,0 +1,360 @@ +/* + * 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 java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.basic.Machines; +import org.apache.brooklyn.sensor.ssh.SshEffectorTasks; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.Jsonya.Navigator; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.TaskTags; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.ssh.BashCommands; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; + +/** + * Creates effectors to start, restart, and stop processes using Chef. + * <p> + * Instances of this should use the {@link ChefConfig} config attributes to configure startup, + * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to determine check-running and stop behaviour. + * Alternatively this can be subclassed and {@link #postStartCustom()} and {@link #stopProcessesAtMachine()} overridden. + * + * @since 0.6.0 + **/ +@Beta +public class ChefLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements ChefConfig { + + private static final Logger log = LoggerFactory.getLogger(ChefLifecycleEffectorTasks.class); + + protected String _pidFile, _serviceName, _windowsServiceName; + + public ChefLifecycleEffectorTasks() { + } + + public ChefLifecycleEffectorTasks usePidFile(String pidFile) { + this._pidFile = pidFile; + return this; + } + public ChefLifecycleEffectorTasks useService(String serviceName) { + this._serviceName = serviceName; + return this; + } + public ChefLifecycleEffectorTasks useWindowsService(String serviceName) { + this._windowsServiceName = serviceName; + return this; + } + + public String getPidFile() { + if (_pidFile!=null) return _pidFile; + return _pidFile = entity().getConfig(ChefConfig.PID_FILE); + } + + public String getServiceName() { + if (_serviceName!=null) return _serviceName; + return _serviceName = entity().getConfig(ChefConfig.SERVICE_NAME); + } + + protected String getNodeName() { + // (node name is needed so we can node delete it) + + // TODO would be better if CHEF_NODE_NAME were a freemarker template, could access entity.id, or hostname, etc, + // in addition to supporting hard-coded node names (which is all we support so far). + + String nodeName = entity().getConfig(ChefConfig.CHEF_NODE_NAME); + if (Strings.isNonBlank(nodeName)) return Strings.makeValidFilename(nodeName); + // node name is taken from ID of this entity, if not specified + return entity().getId(); + } + + public String getWindowsServiceName() { + if (_windowsServiceName!=null) return _windowsServiceName; + return _windowsServiceName = entity().getConfig(ChefConfig.WINDOWS_SERVICE_NAME); + } + + @Override + public void attachLifecycleEffectors(Entity entity) { + if (getPidFile()==null && getServiceName()==null && getClass().equals(ChefLifecycleEffectorTasks.class)) { + // warn on incorrect usage + log.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " + + "in order for check-running and stop to work"); + } + + super.attachLifecycleEffectors(entity); + } + + public static ChefModes detectChefMode(Entity entity) { + ChefModes mode = entity.getConfig(ChefConfig.CHEF_MODE); + if (mode == ChefModes.AUTODETECT) { + // TODO server via API + ProcessTaskWrapper<Boolean> installCheck = DynamicTasks.queue( + ChefServerTasks.isKnifeInstalled()); + mode = installCheck.get() ? ChefModes.KNIFE : ChefModes.SOLO; + log.debug("Using Chef in "+mode+" mode due to autodetect exit code "+installCheck.getExitCode()); + } + Preconditions.checkNotNull(mode, "Non-null "+ChefConfig.CHEF_MODE+" required for "+entity); + return mode; + } + + @Override + protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) { + ChefModes mode = detectChefMode(entity()); + switch (mode) { + case KNIFE: + startWithKnifeAsync(); + break; + + case SOLO: + startWithChefSoloAsync(); + break; + + default: + throw new IllegalStateException("Unknown Chef mode "+mode+" when starting processes for "+entity()); + } + + return "chef start tasks submitted ("+mode+")"; + } + + protected String getPrimaryCookbook() { + return entity().getConfig(CHEF_COOKBOOK_PRIMARY_NAME); + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + protected void startWithChefSoloAsync() { + String baseDir = MachineLifecycleEffectorTasks.resolveOnBoxDir(entity(), Machines.findUniqueSshMachineLocation(entity().getLocations()).get()); + String installDir = Urls.mergePaths(baseDir, "installs/chef"); + + @SuppressWarnings("rawtypes") + Map<String, String> cookbooks = (Map) + ConfigBag.newInstance( entity().getConfig(CHEF_COOKBOOK_URLS) ) + .putIfAbsent( entity().getConfig(CHEF_COOKBOOKS) ) + .getAllConfig(); + if (cookbooks.isEmpty()) + log.warn("No cookbook_urls set for "+entity()+"; launch will likely fail subsequently"); + DynamicTasks.queue( + ChefSoloTasks.installChef(installDir, false), + ChefSoloTasks.installCookbooks(installDir, cookbooks, false)); + + // TODO chef for and run a prestart recipe if necessary + // TODO open ports + + String primary = getPrimaryCookbook(); + + // put all config under brooklyn/cookbook/config + Navigator<MutableMap<Object, Object>> attrs = Jsonya.newInstancePrimitive().at("brooklyn"); + if (Strings.isNonBlank(primary)) attrs.at(primary); + attrs.at("config"); + attrs.put( entity().getAllConfigBag().getAllConfig() ); + // and put launch attrs at root + try { + attrs.root().put((Map<?,?>)Tasks.resolveDeepValue(entity().getConfig(CHEF_LAUNCH_ATTRIBUTES), Object.class, entity().getExecutionContext())); + } catch (Exception e) { Exceptions.propagate(e); } + + Collection<? extends String> runList = entity().getConfig(CHEF_LAUNCH_RUN_LIST); + if (runList==null) runList = entity().getConfig(CHEF_RUN_LIST); + if (runList==null) { + if (Strings.isNonBlank(primary)) runList = ImmutableList.of(primary+"::"+"start"); + else throw new IllegalStateException("Require a primary cookbook or a run_list to effect "+"start"+" on "+entity()); + } + + String runDir = Urls.mergePaths(baseDir, + "apps/"+entity().getApplicationId()+"/chef/entities/"+entity().getEntityType().getSimpleName()+"_"+entity().getId()); + + DynamicTasks.queue(ChefSoloTasks.buildChefFile(runDir, installDir, "launch", + runList, (Map<String, Object>) attrs.root().get())); + + DynamicTasks.queue(ChefSoloTasks.runChef(runDir, "launch", entity().getConfig(CHEF_RUN_CONVERGE_TWICE))); + } + + @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) + protected void startWithKnifeAsync() { + // TODO prestart, ports (as above); also, note, some aspects of this are untested as we need a chef server + + String primary = getPrimaryCookbook(); + + // put all config under brooklyn/cookbook/config + Navigator<MutableMap<Object, Object>> attrs = Jsonya.newInstancePrimitive().at("brooklyn"); + if (Strings.isNonBlank(primary)) attrs.at(primary); + attrs.at("config"); + attrs.put( entity().getAllConfigBag().getAllConfig() ); + // and put launch attrs at root + try { + attrs.root().put((Map<?,?>)Tasks.resolveDeepValue(entity().getConfig(CHEF_LAUNCH_ATTRIBUTES), Object.class, entity().getExecutionContext())); + } catch (Exception e) { Exceptions.propagate(e); } + + Collection<? extends String> runList = entity().getConfig(CHEF_LAUNCH_RUN_LIST); + if (runList==null) runList = entity().getConfig(CHEF_RUN_LIST); + if (runList==null) { + if (Strings.isNonBlank(primary)) runList = ImmutableList.of(primary+"::"+"start"); + else throw new IllegalStateException("Require a primary cookbook or a run_list to effect "+"start"+" on "+entity()); + } + + DynamicTasks.queue( + ChefServerTasks.knifeConvergeTask() + .knifeNodeName(getNodeName()) + .knifeRunList(Strings.join(runList, ",")) + .knifeAddAttributes((Map<? extends Object, ? extends Object>)(Map) attrs.root().get()) + .knifeRunTwice(entity().getConfig(CHEF_RUN_CONVERGE_TWICE)) ); + } + + protected void postStartCustom() { + boolean result = false; + result |= tryCheckStartPid(); + result |= tryCheckStartService(); + result |= tryCheckStartWindowsService(); + if (!result) { + log.warn("No way to check whether "+entity()+" is running; assuming yes"); + } + entity().setAttribute(SoftwareProcess.SERVICE_UP, true); + } + + protected boolean tryCheckStartPid() { + if (getPidFile()==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(getPidFile()).runAsRoot()).get()) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (pid file "+getPidFile()+")"); + } + + // and set the PID + entity().setAttribute(Attributes.PID, + Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+getPidFile()).runAsRoot()).block().getStdout().trim())); + return true; + } + + protected boolean tryCheckStartService() { + if (getServiceName()==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+getServiceName()+" status").runAsRoot()).get())) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+getServiceName()+")"); + } + + return true; + } + + protected boolean tryCheckStartWindowsService() { + if (getWindowsServiceName()==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+getWindowsServiceName()+"\" | find \"RUNNING\"").runAsCommand()).get())) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (windowsService "+getWindowsServiceName()+")"); + } + + return true; + } + + @Override + protected String stopProcessesAtMachine() { + boolean result = false; + result |= tryStopService(); + result |= tryStopWindowsService(); + result |= tryStopPid(); + if (!result) { + throw new IllegalStateException("The process for "+entity()+" could not be stopped (no impl!)"); + } + return "stopped"; + } + + @Override + protected StopMachineDetails<Integer> stopAnyProvisionedMachines() { + if (detectChefMode(entity())==ChefModes.KNIFE) { + DynamicTasks.queue( + // if this task fails show it as failed but don't block subsequent routines + // (ie allow us to actually decommission the machine) + // TODO args could be a List<String> config key ? + TaskTags.markInessential( + new KnifeTaskFactory<String>("delete node and client registration at chef server") + .add("knife node delete "+getNodeName()+" -y") + .add("knife client delete "+getNodeName()+" -y") + .requiringZeroAndReturningStdout() + .newTask() )); + } + + return super.stopAnyProvisionedMachines(); + } + + protected boolean tryStopService() { + if (getServiceName()==null) return false; + int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+getServiceName()+" stop").runAsRoot()).get(); + if (0==result) return true; + if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING) + return true; + + throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)"); + } + + protected boolean tryStopWindowsService() { + if (getWindowsServiceName()==null) return false; + int result = DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+getWindowsServiceName()+"\"").runAsCommand()).get(); + if (0==result) return true; + if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)!=Lifecycle.RUNNING) + return true; + + throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)"); + } + + protected boolean tryStopPid() { + Integer pid = entity().getAttribute(Attributes.PID); + if (pid==null) { + if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)==Lifecycle.RUNNING && getPidFile()==null) + log.warn("No PID recorded for "+entity()+" when running, with PID file "+getPidFile()+"; skipping kill in "+Tasks.current()); + else + if (log.isDebugEnabled()) + log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL)+" / "+getPidFile()+")"); + return false; + } + + // allow non-zero exit as process may have already been killed + DynamicTasks.queue(SshEffectorTasks.ssh( + "kill "+pid, "sleep 5", BashCommands.ok("kill -9 "+pid)).allowingNonZeroExitCode().runAsRoot()).block(); + + if (DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) { + throw new IllegalStateException("Process for "+entity()+" in "+pid+" still running after kill"); + } + entity().setAttribute(Attributes.PID, null); + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java new file mode 100644 index 0000000..d8132be --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefServerTasks.java @@ -0,0 +1,97 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.KeyPair; + +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.core.crypto.SecureKeys; + +import com.google.common.base.Throwables; +import com.google.common.io.Files; + +public class ChefServerTasks { + + private static File chefKeyDir; + + private synchronized static File getExtractedKeysDir() { + if (chefKeyDir==null) { + chefKeyDir = Files.createTempDir(); + chefKeyDir.deleteOnExit(); + } + return chefKeyDir; + } + + /** extract key to a temp file, but one per machine, scheduled for deletion afterwards; + * we extract the key because chef has no way to accept passphrases at present */ + synchronized static File extractKeyFile(SshMachineLocation machine) { + File f = new File(getExtractedKeysDir(), machine.getAddress().getHostName()+".pem"); + if (f.exists()) return f; + KeyPair data = machine.findKeyPair(); + if (data==null) return null; + try { + f.deleteOnExit(); + Files.write(SecureKeys.stringPem(data), f, Charset.defaultCharset()); + return f; + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public static KnifeTaskFactory<Boolean> isKnifeInstalled() { + return new KnifeTaskFactory<Boolean>("knife install check") + .knifeAddParameters("node list") + .notThrowingOnCommonKnifeErrors() + .returningIsExitCodeZero(); + } + + /** plain knife converge task - run list must be set, other arguments are optional */ + public static KnifeConvergeTaskFactory<String> knifeConvergeTask() { + return new KnifeConvergeTaskFactory<String>("knife converge") + .requiringZeroAndReturningStdout(); + } + /** knife converge task configured for this run list (and sudo) */ + public static KnifeConvergeTaskFactory<String> knifeConvergeRunList(String runList) { + return knifeConvergeTask() + .knifeRunList(runList) + .knifeSudo(true); + } + + /** knife converge task configured for this run list on windows (ssh) */ + public static KnifeConvergeTaskFactory<String> knifeConvergeRunListWindowsSsh(String runList) { + return knifeConvergeTask() + .knifeRunList(runList) + .knifeSudo(false) + .knifeAddExtraBootstrapParameters("windows ssh"); + } + + /** knife converge task configured for this run list on windows (winrm) */ + public static KnifeConvergeTaskFactory<String> knifeConvergeRunListWindowsWinrm(String runList) { + return knifeConvergeTask() + .knifeRunList(runList) + .knifeSudo(false) + .knifeAddExtraBootstrapParameters("windows winrm") + .knifePortUseKnifeDefault(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java new file mode 100644 index 0000000..0f4dd40 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloDriver.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.mgmt.TaskAdaptable; +import org.apache.brooklyn.api.mgmt.TaskFactory; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.core.task.DynamicTasks; + +import com.google.common.annotations.Beta; +import com.google.common.reflect.TypeToken; + +/** Driver class to facilitate use of Chef */ +@Beta +@Deprecated /** @deprecated since 0.7.0 use ChefEntity or ChefLifecycleEffectorTasks */ +public class ChefSoloDriver extends AbstractSoftwareProcessSshDriver implements ChefConfig { + + @SuppressWarnings("serial") + public static final ConfigKey<TaskFactory<? extends TaskAdaptable<Boolean>>> IS_RUNNING_TASK = ConfigKeys.newConfigKey( + new TypeToken<TaskFactory<? extends TaskAdaptable<Boolean>>>() {}, + "brooklyn.chef.task.driver.isRunningTask"); + + @SuppressWarnings("serial") + public static final ConfigKey<TaskFactory<?>> STOP_TASK = ConfigKeys.newConfigKey( + new TypeToken<TaskFactory<?>>() {}, + "brooklyn.chef.task.driver.stopTask"); + + public ChefSoloDriver(EntityLocal entity, SshMachineLocation location) { + super(entity, location); + } + + @Override + public void install() { + // TODO flag to force reinstallation + DynamicTasks.queue( + ChefSoloTasks.installChef(getInstallDir(), false), + ChefSoloTasks.installCookbooks(getInstallDir(), getRequiredConfig(CHEF_COOKBOOKS), false)); + } + + @Override + public void customize() { + DynamicTasks.queue(ChefSoloTasks.buildChefFile(getRunDir(), getInstallDir(), "launch", getRequiredConfig(CHEF_RUN_LIST), + getEntity().getConfig(CHEF_LAUNCH_ATTRIBUTES))); + } + + @Override + public void launch() { + DynamicTasks.queue(ChefSoloTasks.runChef(getRunDir(), "launch", getEntity().getConfig(CHEF_RUN_CONVERGE_TWICE))); + } + + @Override + public boolean isRunning() { + return DynamicTasks.queue(getRequiredConfig(IS_RUNNING_TASK)).asTask().getUnchecked(); + } + + @Override + public void stop() { + DynamicTasks.queue(getRequiredConfig(STOP_TASK)); + } + + protected <T> T getRequiredConfig(ConfigKey<T> key) { + return ChefConfigs.getRequiredConfig(getEntity(), key); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java new file mode 100644 index 0000000..64a27a4 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefSoloTasks.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.Map; + +import org.apache.brooklyn.api.mgmt.TaskFactory; +import org.apache.brooklyn.sensor.ssh.SshEffectorTasks; +import org.apache.brooklyn.util.ssh.BashCommands; + +import com.google.common.annotations.Beta; + +@Beta +public class ChefSoloTasks { + + public static TaskFactory<?> installChef(String chefDirectory, boolean force) { + // TODO check on entity whether it is chef _server_ + String installCmd = cdAndRun(chefDirectory, ChefBashCommands.INSTALL_FROM_OPSCODE); + if (!force) installCmd = BashCommands.alternatives("which chef-solo", installCmd); + return SshEffectorTasks.ssh(installCmd).summary("install chef"); + } + + public static TaskFactory<?> installCookbooks(final String chefDirectory, final Map<String,String> cookbooksAndUrls, final boolean force) { + return ChefTasks.installCookbooks(chefDirectory, cookbooksAndUrls, force); + } + + public static TaskFactory<?> installCookbook(String chefDirectory, String cookbookName, String cookbookArchiveUrl, boolean force) { + return ChefTasks.installCookbook(chefDirectory, cookbookName, cookbookArchiveUrl, force); + } + + protected static String cdAndRun(String targetDirectory, String command) { + return BashCommands.chain("mkdir -p "+targetDirectory, + "cd "+targetDirectory, + command); + } + + public static TaskFactory<?> buildChefFile(String runDirectory, String chefDirectory, String phase, Iterable<? extends String> runList, + Map<String, Object> optionalAttributes) { + return ChefTasks.buildChefFile(runDirectory, chefDirectory, phase, runList, optionalAttributes); + } + + public static TaskFactory<?> runChef(String runDir, String phase) { + return runChef(runDir, phase, false); + } + /** see {@link ChefConfig#CHEF_RUN_CONVERGE_TWICE} for background on why 'twice' is available */ + public static TaskFactory<?> runChef(String runDir, String phase, Boolean twice) { + String cmd = "sudo chef-solo -c "+phase+".rb -j "+phase+".json -ldebug"; + if (twice!=null && twice) cmd = BashCommands.alternatives(cmd, cmd); + + return SshEffectorTasks.ssh(cdAndRun(runDir, cmd)). + summary("run chef for "+phase).requiringExitCodeZero(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java new file mode 100644 index 0000000..0f359d5 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/ChefTasks.java @@ -0,0 +1,153 @@ +/* + * 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 java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.TaskAdaptable; +import org.apache.brooklyn.api.mgmt.TaskFactory; +import org.apache.brooklyn.effector.core.EffectorTasks; +import org.apache.brooklyn.sensor.ssh.SshEffectorTasks; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.file.ArchiveTasks; +import org.apache.brooklyn.util.core.file.ArchiveUtils.ArchiveType; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.TaskBuilder; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.ssh.BashCommands; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +@Beta +public class ChefTasks { + + private static final Logger log = LoggerFactory.getLogger(ChefTasks.class); + + public static TaskFactory<?> installChef(String chefDirectory, boolean force) { + // TODO check on entity whether it is chef _server_ + String installCmd = cdAndRun(chefDirectory, ChefBashCommands.INSTALL_FROM_OPSCODE); + if (!force) installCmd = BashCommands.alternatives("which chef-solo", installCmd); + return SshEffectorTasks.ssh(installCmd).summary("install chef"); + } + + public static TaskFactory<?> installCookbooks(final String chefDirectory, final Map<String,String> cookbooksAndUrls, final boolean force) { + return Tasks.<Void>builder().name("install "+(cookbooksAndUrls==null ? "0" : cookbooksAndUrls.size())+" cookbook"+Strings.s(cookbooksAndUrls)).body( + new Runnable() { + public void run() { + Entity e = EffectorTasks.findEntity(); + if (cookbooksAndUrls==null) + throw new IllegalStateException("No cookbooks defined to install at "+e); + for (String cookbook: cookbooksAndUrls.keySet()) + DynamicTasks.queue(installCookbook(chefDirectory, cookbook, cookbooksAndUrls.get(cookbook), force)); + } + }).buildFactory(); + } + + public static TaskFactory<?> installCookbook(final String chefDirectory, final String cookbookName, final String cookbookArchiveUrl, final boolean force) { + return new TaskFactory<TaskAdaptable<?>>() { + @Override + public TaskAdaptable<?> newTask() { + TaskBuilder<Void> tb = Tasks.<Void>builder().name("install cookbook "+cookbookName); + + String cookbookDir = Urls.mergePaths(chefDirectory, cookbookName); + String privateTmpDirContainingUnpackedCookbook = + Urls.mergePaths(chefDirectory, "tmp-"+Strings.makeValidFilename(cookbookName)+"-"+Identifiers.makeRandomId(4)); + + // TODO - skip the install earlier if it exists and isn't forced +// if (!force) { +// // in builder.body, check +// // "ls "+cookbookDir +// // and stop if it's zero +// // remove reference to 'force' below +// } + + String destName = null; + if (ArchiveType.of(cookbookArchiveUrl)==ArchiveType.UNKNOWN) { + destName = cookbookName + ".tgz"; + log.debug("Assuming TGZ type for chef cookbook url "+cookbookArchiveUrl+"; it will be downloaded as "+destName); + } + tb.add(ArchiveTasks.deploy(null, null, cookbookArchiveUrl, EffectorTasks.findSshMachine(), privateTmpDirContainingUnpackedCookbook, + false, null, destName).newTask()); + + String installCmd = BashCommands.chain( + "cd "+privateTmpDirContainingUnpackedCookbook, + "COOKBOOK_EXPANDED_DIR=`ls`", + BashCommands.requireTest("`ls | wc -w` -eq 1", + "The deployed archive "+cookbookArchiveUrl+" must contain exactly one directory"), + "mv $COOKBOOK_EXPANDED_DIR '../"+cookbookName+"'", + "cd ..", + "rm -rf '"+privateTmpDirContainingUnpackedCookbook+"'"); + + installCmd = force ? BashCommands.alternatives("rm -rf "+cookbookDir, installCmd) : BashCommands.alternatives("ls "+cookbookDir+" > /dev/null 2> /dev/null", installCmd); + tb.add(SshEffectorTasks.ssh(installCmd).summary("renaming cookbook dir").requiringExitCodeZero().newTask()); + + return tb.build(); + } + }; + } + + protected static String cdAndRun(String targetDirectory, String command) { + return BashCommands.chain("mkdir -p '"+targetDirectory+"'", + "cd '"+targetDirectory+"'", + command); + } + + public static TaskFactory<?> buildChefFile(String runDirectory, String chefDirectory, String phase, Iterable<? extends String> runList, + Map<String, Object> optionalAttributes) { + // TODO if it's server, try knife first + // TODO configure add'l properties + String phaseRb = + "root = " + + "'"+runDirectory+"'" + // recommended alternate to runDir is the following, but it is not available in some rubies + //+ File.absolute_path(File.dirname(__FILE__))"+ + + "\n"+ + "file_cache_path root\n"+ +// "cookbook_path root + '/cookbooks'\n"; + "cookbook_path '"+chefDirectory+"'\n"; + + Map<String,Object> phaseJsonMap = MutableMap.of(); + if (optionalAttributes!=null) + phaseJsonMap.putAll(optionalAttributes); + if (runList!=null) + phaseJsonMap.put("run_list", ImmutableList.copyOf(runList)); + Gson json = new GsonBuilder().create(); + String phaseJson = json.toJson(phaseJsonMap); + + return Tasks.sequential("build chef files for "+phase, + SshEffectorTasks.put(Urls.mergePaths(runDirectory)+"/"+phase+".rb").contents(phaseRb).createDirectory(), + SshEffectorTasks.put(Urls.mergePaths(runDirectory)+"/"+phase+".json").contents(phaseJson)); + } + + public static TaskFactory<?> runChef(String runDir, String phase) { + // TODO chef server + return SshEffectorTasks.ssh(cdAndRun(runDir, "sudo chef-solo -c "+phase+".rb -j "+phase+".json -ldebug")). + summary("run chef for "+phase).requiringExitCodeZero(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java new file mode 100644 index 0000000..39e31b8 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeConvergeTaskFactory.java @@ -0,0 +1,246 @@ +/* + * 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.checkNotNull; +import static org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes.wrapBash; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Strings; +import com.google.common.net.HostAndPort; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.effector.core.EffectorTasks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.ssh.BashCommands; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.gson.GsonBuilder; + +public class KnifeConvergeTaskFactory<RET> extends KnifeTaskFactory<RET> { + + private static final Logger log = LoggerFactory.getLogger(KnifeConvergeTaskFactory.class); + + protected Function<? super Entity,String> runList; + protected Map<Object, Object> knifeAttributes = new MutableMap<Object, Object>(); + protected List<String> extraBootstrapParameters = MutableList.of(); + protected Boolean sudo; + protected Boolean runTwice; + protected String nodeName; + protected Integer port; + /** null means nothing specified, use user supplied or machine default; + * false means use machine default (disallow user supplied); + * true means use knife default (omit the argument and disallow user supplied) + */ + protected Boolean portOmittedToUseKnifeDefault; + + public KnifeConvergeTaskFactory(String taskName) { + super(taskName); + } + + @Override + protected KnifeConvergeTaskFactory<RET> self() { + return this; + } + + /** construct the knife command, based on the settings on other methods + * (called when instantiating the script, after all parameters sent) + */ + protected List<String> initialKnifeParameters() { + // runs inside the task so can detect entity/machine at runtime + MutableList<String> result = new MutableList<String>(); + SshMachineLocation machine = EffectorTasks.findSshMachine(); + + result.add("bootstrap"); + result.addAll(extraBootstrapParameters); + + HostAndPort hostAndPort = machine.getSshHostAndPort(); + result.add(wrapBash(hostAndPort.getHostText())); + Integer whichPort = knifeWhichPort(hostAndPort); + if (whichPort!=null) + result.add("-p "+whichPort); + + result.add("-x "+wrapBash(checkNotNull(machine.getUser(), "user"))); + + File keyfile = ChefServerTasks.extractKeyFile(machine); + if (keyfile!=null) result.add("-i "+keyfile.getPath()); + else result.add("-P "+checkNotNull(machine.findPassword(), "No password or private key data for "+machine)); + + result.add("--no-host-key-verify"); + + if (sudo != Boolean.FALSE) result.add("--sudo"); + + if (!Strings.isNullOrEmpty(nodeName)) { + result.add("--node-name"); + result.add(nodeName); + } + + result.add("-r "+wrapBash(runList.apply(entity()))); + + if (!knifeAttributes.isEmpty()) + result.add("-j "+wrapBash(new GsonBuilder().create() + .toJson(knifeAttributes))); + + return result; + } + + /** whether knife should attempt to run twice; + * see {@link ChefConfig#CHEF_RUN_CONVERGE_TWICE} */ + public KnifeConvergeTaskFactory<RET> knifeRunTwice(boolean runTwice) { + this.runTwice = runTwice; + return self(); + } + + /** whether to pass --sudo to knife; default true */ + public KnifeConvergeTaskFactory<RET> knifeSudo(boolean sudo) { + this.sudo = sudo; + return self(); + } + + /** what node name to pass to knife; default = null, meaning chef-client will pick the node name */ + public KnifeConvergeTaskFactory<RET> knifeNodeName(String nodeName) { + this.nodeName = nodeName; + return self(); + } + + /** tell knife to use an explicit port */ + public KnifeConvergeTaskFactory<RET> knifePort(int port) { + if (portOmittedToUseKnifeDefault!=null) { + log.warn("Port "+port+" specified to "+this+" for when already explicitly told to use a default (overriding previous); see subsequent warning for more details"); + } + this.port = port; + return self(); + } + + /** omit the port parameter altogether (let knife use its default) */ + public KnifeConvergeTaskFactory<RET> knifePortUseKnifeDefault() { + if (port!=null) { + log.warn("knifePortUseKnifeDefault specified to "+this+" when already told to use "+port+" explicitly (overriding previous); see subsequent warning for more details"); + port = -1; + } + portOmittedToUseKnifeDefault = true; + return self(); + } + + /** use the default port known to brooklyn for the target machine for ssh */ + public KnifeConvergeTaskFactory<RET> knifePortUseMachineSshPort() { + if (port!=null) { + log.warn("knifePortUseMachineSshPort specified to "+this+" when already told to use "+port+" explicitly (overriding previous); see subsequent warning for more details"); + port = -1; + } + portOmittedToUseKnifeDefault = false; + return self(); + } + + protected Integer knifeWhichPort(HostAndPort hostAndPort) { + if (port==null) { + if (Boolean.TRUE.equals(portOmittedToUseKnifeDefault)) + // user has explicitly said to use knife default, omitting port here + return null; + // default is to use the machine port + return hostAndPort.getPort(); + } + if (port==-1) { + // port was supplied by user, then portDefault (true or false) + port = null; + Integer whichPort = knifeWhichPort(hostAndPort); + log.warn("knife port conflicting instructions for "+this+" at entity "+entity()+" on "+hostAndPort+"; using default ("+whichPort+")"); + return whichPort; + } + if (portOmittedToUseKnifeDefault!=null) { + // portDefault was specified (true or false), then overridden with a port + log.warn("knife port conflicting instructions for "+this+" at entity "+entity()+" on "+hostAndPort+"; using supplied port "+port); + } + // port was supplied by user, use that + return port; + } + + /** parameters to pass to knife after the bootstrap command */ + public KnifeConvergeTaskFactory<RET> knifeAddExtraBootstrapParameters(String extraBootstrapParameter1, String ...extraBootstrapParameters) { + this.extraBootstrapParameters.add(extraBootstrapParameter1); + for (String p: extraBootstrapParameters) + this.extraBootstrapParameters.add(p); + return self(); + } + + /** function supplying the run list to be passed to knife, evaluated at the last moment */ + public KnifeConvergeTaskFactory<RET> knifeRunList(Function<? super Entity, String> runList) { + this.runList = runList; + return self(); + } + public KnifeConvergeTaskFactory<RET> knifeRunList(String runList) { + this.runList = Functions.constant(runList); + return self(); + } + + /** includes the given attributes in the attributes to be passed to chef; + * when combining with other attributes, this uses {@link Jsonya} semantics to add + * (a deep add, combining lists and maps) */ + public KnifeConvergeTaskFactory<RET> knifeAddAttributes(Map<? extends Object, ? extends Object> attributes) { + if (attributes!=null && !attributes.isEmpty()) { + Jsonya.of(knifeAttributes).add(attributes); + } + return self(); + } + + protected String buildKnifeCommand(int knifeCommandIndex) { + String result = super.buildKnifeCommand(knifeCommandIndex); + if (Boolean.TRUE.equals(runTwice)) + result = BashCommands.alternatives(result, result); + return result; + } + + @Override + public <T2> KnifeConvergeTaskFactory<T2> returning(ScriptReturnType type) { + return (KnifeConvergeTaskFactory<T2>) super.<T2>returning(type); + } + + @Override + public <RET2> KnifeConvergeTaskFactory<RET2> returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) { + return (KnifeConvergeTaskFactory<RET2>) super.returning(resultTransformation); + } + + @Override + public KnifeConvergeTaskFactory<Boolean> returningIsExitCodeZero() { + return (KnifeConvergeTaskFactory<Boolean>) super.returningIsExitCodeZero(); + } + + @Override + public KnifeConvergeTaskFactory<String> requiringZeroAndReturningStdout() { + return (KnifeConvergeTaskFactory<String>) super.requiringZeroAndReturningStdout(); + } + + public KnifeConvergeTaskFactory<RET> knifeAddParameters(String word1, String ...words) { + super.knifeAddParameters(word1, words); + return self(); + } + + // TODO other methods from KnifeTaskFactory will return KTF class not KCTF; + // should make it generic so it returns the right type... +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java new file mode 100644 index 0000000..5d1af9d --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/chef/KnifeTaskFactory.java @@ -0,0 +1,240 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.internal.ssh.process.ProcessTool; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.core.task.system.internal.SystemProcessTaskFactory; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; + +import com.google.common.base.Function; + +/** A factory which acts like {@link ProcessTaskFactory} with special options for knife. + * Typical usage is to {@link #addKnifeParameters(String)}s for the knife command to be run. + * You can also {@link #add(String...)} commands as needed; these will run *before* knife, + * unless you addKnifeCommandHere(). + * <p> + * This impl will use sensible defaults, including {@link ConfigKey}s on the context entity, + * for general knife config but not specific commands etc. It supports: + * <li> {@link ChefConfig#KNIFE_EXECUTABLE} + * <li> {@link ChefConfig#KNIFE_CONFIG_FILE} + * <p> + * (Other fields will typically be used by methods calling to this factory.) + * */ +// see e.g. http://docs.opscode.com/knife_bootstrap.html +public class KnifeTaskFactory<RET> extends SystemProcessTaskFactory<KnifeTaskFactory<RET>, RET>{ + + private static String KNIFE_PLACEHOLDER = "<knife command goes here 1234>"; + public final String taskName; + protected String knifeExecutable; + protected List<String> knifeParameters = new ArrayList<String>(); + protected String knifeConfigFile; + protected String knifeSetupCommands; + protected Boolean throwOnCommonKnifeErrors; + + public KnifeTaskFactory(String taskName) { + this.taskName = taskName; + summary(taskName); + // knife setup usually requires a login shell + config.put(ProcessTool.PROP_LOGIN_SHELL, true); + } + + @Override + public List<Function<ProcessTaskWrapper<?>, Void>> getCompletionListeners() { + MutableList<Function<ProcessTaskWrapper<?>, Void>> result = MutableList.copyOf(super.getCompletionListeners()); + if (throwOnCommonKnifeErrors != Boolean.FALSE) + insertKnifeCompletionListenerIntoCompletionListenersList(result); + return result.asUnmodifiable(); + } + + public KnifeTaskFactory<RET> notThrowingOnCommonKnifeErrors() { + throwOnCommonKnifeErrors = false; + return self(); + } + + protected void insertKnifeCompletionListenerIntoCompletionListenersList(List<Function<ProcessTaskWrapper<?>, Void>> listeners) { + // give a nice warning if chef/knife not set up correctly + Function<ProcessTaskWrapper<?>, Void> propagateIfKnifeConfigFileMissing = new Function<ProcessTaskWrapper<?>, Void>() { + public Void apply(@Nullable ProcessTaskWrapper<?> input) { + if (input.getExitCode()!=0 && input.getStderr().indexOf("WARNING: No knife configuration file found")>=0) { + String myConfig = knifeConfigFileOption(); + if (Strings.isEmpty(myConfig)) + throw new IllegalStateException("Config file for Chef knife must be specified in "+ChefConfig.KNIFE_CONFIG_FILE+" (or valid knife default set up)"); + else + throw new IllegalStateException("Error reading config file for Chef knife ("+myConfig+") -- does it exist?"); + } + return null; + } + }; + listeners.add(propagateIfKnifeConfigFileMissing); + } + + + @Override + public ProcessTaskWrapper<RET> newTask() { + return new SystemProcessTaskWrapper("Knife"); + } + + /** Inserts the knife command at the current place in the list. + * Can be run multiple times. The knife command added at the end of the list + * if this is not invoked (and it is the only command if nothing is {@link #add(String...)}ed. + */ + public KnifeTaskFactory<RET> addKnifeCommandToScript() { + add(KNIFE_PLACEHOLDER); + return self(); + } + + @Override + public List<String> getCommands() { + MutableList<String> result = new MutableList<String>(); + String setupCommands = knifeSetupCommands(); + if (setupCommands != null && Strings.isNonBlank(setupCommands)) + result.add(setupCommands); + int numKnifes = 0; + for (String c: super.getCommands()) { + if (c==KNIFE_PLACEHOLDER) + result.add(buildKnifeCommand(numKnifes++)); + else + result.add(c); + } + if (numKnifes==0) + result.add(buildKnifeCommand(numKnifes++)); + return result.asUnmodifiable(); + } + + /** creates the command for running knife. + * in some cases knife may be added multiple times, + * and in that case the parameter here tells which time it is being added, + * on a single run. */ + protected String buildKnifeCommand(int knifeCommandIndex) { + MutableList<String> words = new MutableList<String>(); + words.add(knifeExecutable()); + words.addAll(initialKnifeParameters()); + words.addAll(knifeParameters()); + String x = knifeConfigFileOption(); + if (Strings.isNonBlank(x)) words.add(knifeConfigFileOption()); + return Strings.join(words, " "); + } + + /** allows a way for subclasses to build up parameters at the start */ + protected List<String> initialKnifeParameters() { + return new MutableList<String>(); + } + + @Nullable /** callers should allow this to be null so task can be used outside of an entity */ + protected Entity entity() { + return BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); + } + protected <T> T entityConfig(ConfigKey<T> key) { + Entity entity = entity(); + if (entity!=null) + return entity.getConfig(key); + return null; + } + + public KnifeTaskFactory<RET> knifeExecutable(String knifeExecutable) { + this.knifeExecutable = knifeExecutable; + return this; + } + + protected String knifeExecutable() { + if (knifeExecutable!=null) return knifeExecutable; + + String knifeExecFromConfig = entityConfig(ChefConfig.KNIFE_EXECUTABLE); + if (knifeExecFromConfig!=null) return BashStringEscapes.wrapBash(knifeExecFromConfig); + + // assume on the path, if executable not set + return "knife"; + } + + protected List<String> knifeParameters() { + return knifeParameters; + } + + public KnifeTaskFactory<RET> knifeAddParameters(String word1, String ...words) { + knifeParameters.add(word1); + for (String w: words) + knifeParameters.add(w); + return self(); + } + + public KnifeTaskFactory<RET> knifeConfigFile(String knifeConfigFile) { + this.knifeConfigFile = knifeConfigFile; + return self(); + } + + @Nullable + protected String knifeConfigFileOption() { + if (knifeConfigFile!=null) return "-c "+knifeConfigFile; + + String knifeConfigFileFromConfig = entityConfig(ChefConfig.KNIFE_CONFIG_FILE); + if (knifeConfigFileFromConfig!=null) return "-c "+BashStringEscapes.wrapBash(knifeConfigFileFromConfig); + + // if not supplied will use global config + return null; + } + + public KnifeTaskFactory<RET> knifeSetupCommands(String knifeSetupCommands) { + this.knifeSetupCommands = knifeSetupCommands; + return self(); + } + + @Nullable + protected String knifeSetupCommands() { + if (knifeSetupCommands!=null) return knifeSetupCommands; + + String knifeSetupCommandsFromConfig = entityConfig(ChefConfig.KNIFE_SETUP_COMMANDS); + if (knifeSetupCommandsFromConfig!=null) return knifeSetupCommandsFromConfig; + + // if not supplied will use global config + return null; + } + + @Override + public <T2> KnifeTaskFactory<T2> returning(ScriptReturnType type) { + return (KnifeTaskFactory<T2>) super.<T2>returning(type); + } + + @Override + public <RET2> KnifeTaskFactory<RET2> returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) { + return (KnifeTaskFactory<RET2>) super.returning(resultTransformation); + } + + @Override + public KnifeTaskFactory<Boolean> returningIsExitCodeZero() { + return (KnifeTaskFactory<Boolean>) super.returningIsExitCodeZero(); + } + + @Override + public KnifeTaskFactory<String> requiringZeroAndReturningStdout() { + return (KnifeTaskFactory<String>) super.requiringZeroAndReturningStdout(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java new file mode 100644 index 0000000..81811fb --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaAppUtils.java @@ -0,0 +1,263 @@ +/* + * 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.java; + +import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.management.openmbean.CompositeData; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.core.config.render.RendererHints; +import org.apache.brooklyn.policy.enricher.RollingTimeWindowMeanEnricher; +import org.apache.brooklyn.policy.enricher.TimeFractionDeltaEnricher; +import org.apache.brooklyn.sensor.feed.http.HttpValueFunctions; +import org.apache.brooklyn.sensor.feed.jmx.JmxAttributePollConfig; +import org.apache.brooklyn.sensor.feed.jmx.JmxFeed; +import org.apache.brooklyn.util.math.MathFunctions; +import org.apache.brooklyn.util.text.ByteSizeStrings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.base.Function; + +public class JavaAppUtils { + + public static boolean isEntityMxBeanStatsEnabled(Entity entity) { + return groovyTruth(entity.getConfig(UsesJavaMXBeans.MXBEAN_STATS_ENABLED)); + } + + /** + * @see #connectJavaAppServerPolicies(EntityLocal, Duration) + * @see #getMxBeanSensorsBuilder(EntityLocal) + */ + @Nullable + public static JmxFeed connectMXBeanSensors(EntityLocal entity) { + if (isEntityMxBeanStatsEnabled(entity)) { + return getMxBeanSensorsBuilder(entity).build(); + } else { + return null; + } + } + + /** @see #connectJavaAppServerPolicies(EntityLocal, Duration) */ + @Nullable + public static JmxFeed connectMXBeanSensors(EntityLocal entity, long jmxPollPeriodMs) { + if (isEntityMxBeanStatsEnabled(entity)) { + return getMxBeanSensorsBuilder(entity, jmxPollPeriodMs).build(); + } else { + return null; + } + } + + /** + * @param entity The entity at which to poll + * @param jmxPollPeriod How often to poll + * @return A {@link JmxFeed} configured to poll the given entity at the given period, + * or null if the entity is not configured for MXBEAN_STATS + * @see org.apache.brooklyn.entity.java.UsesJavaMXBeans#MXBEAN_STATS_ENABLED + */ + @Nullable + public static JmxFeed connectMXBeanSensors(EntityLocal entity, Duration jmxPollPeriod) { + if (isEntityMxBeanStatsEnabled(entity)) { + return getMxBeanSensorsBuilder(entity, jmxPollPeriod).build(); + } else { + return null; + } + } + + public static void connectJavaAppServerPolicies(EntityLocal entity) { + connectJavaAppServerPolicies(entity, Duration.TEN_SECONDS); + } + + public static void connectJavaAppServerPolicies(EntityLocal entity, Duration windowPeriod) { + entity.addEnricher(new TimeFractionDeltaEnricher<Double>(entity, UsesJavaMXBeans.PROCESS_CPU_TIME, + UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, TimeUnit.MILLISECONDS)); + + entity.addEnricher(new RollingTimeWindowMeanEnricher<Double>(entity, + UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_IN_WINDOW, + windowPeriod)); + } + + /** + * @param entity The entity at which to poll + * @return A {@link JmxFeed.Builder} configured to poll entity every ten seconds + * @see #getMxBeanSensorsBuilder(EntityLocal, Duration) + */ + @Nonnull + public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity) { + return getMxBeanSensorsBuilder(entity, Duration.TEN_SECONDS); + } + + /** @see #getMxBeanSensorsBuilder(EntityLocal, Duration) */ + @Nonnull + public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity, long jmxPollPeriod) { + return getMxBeanSensorsBuilder(entity, Duration.millis(jmxPollPeriod)); + } + + /** + * @param entity The entity at which to poll + * @param jmxPollPeriod How often to poll + * @return A {@link JmxFeed.Builder} configured to poll many interesting MXBeans + * at the given entity and to repeat according to the given poll period. + * <p/> + * If an entity does not have MXBean stats enabled (i.e. {@link UsesJavaMXBeans#MXBEAN_STATS_ENABLED} is + * configured to false) then returns a builder configured with entity and duration but no polls. + * <p/> + * Use {@link #connectMXBeanSensors(EntityLocal, Duration)} to create and build in one step. + */ + @Nonnull + @SuppressWarnings({"unchecked"}) + public static JmxFeed.Builder getMxBeanSensorsBuilder(EntityLocal entity, Duration jmxPollPeriod) { + JmxFeed.Builder builder = JmxFeed.builder() + .entity(entity) + .period(jmxPollPeriod); + if (isEntityMxBeanStatsEnabled(entity)) { + // TODO Could we reuse the result of compositeDataToMemoryUsage? + builder + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.USED_HEAP_MEMORY) + .objectName(ManagementFactory.MEMORY_MXBEAN_NAME) + .attributeName("HeapMemoryUsage") + .onSuccess((Function) HttpValueFunctions.chain(compositeDataToMemoryUsage(), new Function<MemoryUsage, Long>() { + @Override public Long apply(MemoryUsage input) { + return (input == null) ? null : input.getUsed(); + }}))) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.INIT_HEAP_MEMORY) + .objectName(ManagementFactory.MEMORY_MXBEAN_NAME) + .attributeName("HeapMemoryUsage") + .onSuccess((Function) HttpValueFunctions.chain(compositeDataToMemoryUsage(), new Function<MemoryUsage, Long>() { + @Override public Long apply(MemoryUsage input) { + return (input == null) ? null : input.getInit(); + }}))) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.COMMITTED_HEAP_MEMORY) + .objectName(ManagementFactory.MEMORY_MXBEAN_NAME) + .attributeName("HeapMemoryUsage") + .onSuccess((Function) HttpValueFunctions.chain(compositeDataToMemoryUsage(), new Function<MemoryUsage, Long>() { + @Override public Long apply(MemoryUsage input) { + return (input == null) ? null : input.getCommitted(); + }}))) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.MAX_HEAP_MEMORY) + .objectName(ManagementFactory.MEMORY_MXBEAN_NAME) + .attributeName("HeapMemoryUsage") + .onSuccess((Function) HttpValueFunctions.chain(compositeDataToMemoryUsage(), new Function<MemoryUsage, Long>() { + @Override public Long apply(MemoryUsage input) { + return (input == null) ? null : input.getMax(); + }}))) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.NON_HEAP_MEMORY_USAGE) + .objectName(ManagementFactory.MEMORY_MXBEAN_NAME) + .attributeName("NonHeapMemoryUsage") + .onSuccess((Function) HttpValueFunctions.chain(compositeDataToMemoryUsage(), new Function<MemoryUsage, Long>() { + @Override public Long apply(MemoryUsage input) { + return (input == null) ? null : input.getUsed(); + }}))) + + .pollAttribute(new JmxAttributePollConfig<Integer>(UsesJavaMXBeans.CURRENT_THREAD_COUNT) + .objectName(ManagementFactory.THREAD_MXBEAN_NAME) + .attributeName("ThreadCount")) + .pollAttribute(new JmxAttributePollConfig<Integer>(UsesJavaMXBeans.PEAK_THREAD_COUNT) + .objectName(ManagementFactory.THREAD_MXBEAN_NAME) + .attributeName("PeakThreadCount")) + + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.START_TIME) + .objectName(ManagementFactory.RUNTIME_MXBEAN_NAME) + .period(60, TimeUnit.SECONDS) + .attributeName("StartTime")) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.UP_TIME) + .objectName(ManagementFactory.RUNTIME_MXBEAN_NAME) + .period(60, TimeUnit.SECONDS) + .attributeName("Uptime")) + + .pollAttribute(new JmxAttributePollConfig<Double>(UsesJavaMXBeans.PROCESS_CPU_TIME) + .objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME) + .attributeName("ProcessCpuTime") + .onSuccess((Function) MathFunctions.times(0.001*0.001))) // nanos to millis + .pollAttribute(new JmxAttributePollConfig<Double>(UsesJavaMXBeans.SYSTEM_LOAD_AVERAGE) + .objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME) + .attributeName("SystemLoadAverage")) + .pollAttribute(new JmxAttributePollConfig<Integer>(UsesJavaMXBeans.AVAILABLE_PROCESSORS) + .objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME) + .period(60, TimeUnit.SECONDS) + .attributeName("AvailableProcessors")) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.TOTAL_PHYSICAL_MEMORY_SIZE) + .objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME) + .period(60, TimeUnit.SECONDS) + .attributeName("TotalPhysicalMemorySize")) + .pollAttribute(new JmxAttributePollConfig<Long>(UsesJavaMXBeans.FREE_PHYSICAL_MEMORY_SIZE) + .objectName(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME) + .period(60, TimeUnit.SECONDS) + .attributeName("FreePhysicalMemorySize")); + //FIXME: need a new type of adapter that maps multiple objectNames to a mapping + // jmxAdapter.objectName(ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*").with { + // attribute("SystemLoadAverage").subscribe(UsesJavaMXBeans.GARBAGE_COLLECTION_TIME, { def m -> log.info("XXXXXXX $m") }); + // } + } + return builder; + } + + /** @deprecated Since 0.7.0. Use {@link org.apache.brooklyn.util.math.MathFunctions#times(double)} instead */ + @Deprecated + public static Function<Number, Double> times(final double x) { + return MathFunctions.times(x); + } + + public static Function<CompositeData, MemoryUsage> compositeDataToMemoryUsage() { + return new Function<CompositeData, MemoryUsage>() { + @Override public MemoryUsage apply(CompositeData input) { + return (input == null) ? null : MemoryUsage.from(input); + } + }; + } + + private static final AtomicBoolean initialized = new AtomicBoolean(false); + + /** Setup renderer hints for the MXBean attributes. */ + public static void init() { + if (initialized.get()) return; + synchronized (initialized) { + if (initialized.get()) return; + + RendererHints.register(UsesJavaMXBeans.USED_HEAP_MEMORY, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.INIT_HEAP_MEMORY, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.MAX_HEAP_MEMORY, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.COMMITTED_HEAP_MEMORY, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.NON_HEAP_MEMORY_USAGE, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.TOTAL_PHYSICAL_MEMORY_SIZE, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(UsesJavaMXBeans.FREE_PHYSICAL_MEMORY_SIZE, RendererHints.displayValue(ByteSizeStrings.metric())); + + RendererHints.register(UsesJavaMXBeans.START_TIME, RendererHints.displayValue(Time.toDateString())); + RendererHints.register(UsesJavaMXBeans.UP_TIME, RendererHints.displayValue(Duration.millisToStringRounded())); + RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME, RendererHints.displayValue(Duration.millisToStringRounded())); + RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_LAST, RendererHints.displayValue(MathFunctions.percent(4))); + RendererHints.register(UsesJavaMXBeans.PROCESS_CPU_TIME_FRACTION_IN_WINDOW, RendererHints.displayValue(MathFunctions.percent(4))); + + initialized.set(true); + } + } + + static { + init(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java new file mode 100644 index 0000000..44afc8d --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaEntityMethods.java @@ -0,0 +1,30 @@ +/* + * 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.java; + +import org.apache.brooklyn.config.ConfigKey; + +/** DSL conveniences for Java entities. Also see {@link JavaAppUtils} for methods useful within Java classes. */ +public class JavaEntityMethods { + + public static ConfigKey<String> javaSysProp(String propertyName) { return UsesJava.JAVA_SYSPROPS.subKey(propertyName); } + + // TODO javaMaxHeap javaInitialHeap javaMaxPermGen should all be supplied as ListConfigs on JAVA_OPTIONS + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java new file mode 100644 index 0000000..627a5d2 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JavaSoftwareProcessDriver.java @@ -0,0 +1,30 @@ +/* + * 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.java; + +import org.apache.brooklyn.entity.software.base.SoftwareProcessDriver; + +/** + * A {@link SoftwareProcessDriver} for Java processes. + */ +public interface JavaSoftwareProcessDriver extends SoftwareProcessDriver { + + public boolean isJmxEnabled(); + +}
