You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:26 UTC
[03/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723'
into reorg2
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --cc brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
index 0000000,a58531c..053e8b4
mode 000000,100644..100644
--- a/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
+++ b/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
@@@ -1,0 -1,169 +1,170 @@@
+ /*
+ * 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.rest.util.json;
+
+ import javax.servlet.ServletContext;
+ import javax.ws.rs.core.Context;
+ import javax.ws.rs.core.MediaType;
+
+ import org.apache.brooklyn.api.mgmt.ManagementContext;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
++import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+ import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+ import org.apache.brooklyn.rest.util.OsgiCompat;
+ import org.codehaus.jackson.Version;
+ import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
+ import org.codehaus.jackson.map.ObjectMapper;
+ import org.codehaus.jackson.map.SerializationConfig;
+ import org.codehaus.jackson.map.module.SimpleModule;
+ import org.codehaus.jackson.map.type.TypeFactory;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
+ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements ManagementContextInjectable {
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonJsonProvider.class);
+
+ public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
+
+ @Context protected ServletContext servletContext;
+
+ protected ObjectMapper ourMapper;
+ protected boolean notFound = false;
+
+ private ManagementContext mgmt;
+
+ public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
+ if (ourMapper != null)
+ return ourMapper;
+
+ findSharedMapper();
+
+ if (ourMapper != null)
+ return ourMapper;
+
+ if (!notFound) {
+ log.warn("Management context not available; using default ObjectMapper in "+this);
+ notFound = true;
+ }
+
+ return super.locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
+ }
+
+ protected synchronized ObjectMapper findSharedMapper() {
+ if (ourMapper != null || notFound)
+ return ourMapper;
+
+ ourMapper = findSharedObjectMapper(servletContext, mgmt);
+ if (ourMapper == null) return null;
+
+ if (notFound) {
+ notFound = false;
+ }
+ log.debug("Found mapper "+ourMapper+" for "+this+", creating custom Brooklyn mapper");
+
+ return ourMapper;
+ }
+
+ /**
+ * Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
+ * returns null if a shared instance cannot be created.
+ */
+ public static ObjectMapper findSharedObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
+ if (servletContext != null) {
+ synchronized (servletContext) {
+ ObjectMapper mapper = (ObjectMapper) servletContext.getAttribute(BROOKLYN_REST_OBJECT_MAPPER);
+ if (mapper != null) return mapper;
+
+ mapper = newPrivateObjectMapper(getManagementContext(servletContext));
+ servletContext.setAttribute(BROOKLYN_REST_OBJECT_MAPPER, mapper);
+ return mapper;
+ }
+ }
+ if (mgmt != null) {
+ synchronized (mgmt) {
+ ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
+ ObjectMapper mapper = mgmt.getConfig().getConfig(key);
+ if (mapper != null) return mapper;
+
+ mapper = newPrivateObjectMapper(mgmt);
+ log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
- ((BrooklynProperties)mgmt.getConfig()).put(key, mapper);
++ ((ManagementContextInternal)mgmt).getBrooklynProperties().put(key, mapper);
+ return mapper;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Like {@link #findSharedObjectMapper(ServletContext, ManagementContext)} but will create a private
+ * ObjectMapper if it can, from the servlet context and/or the management context, or else fail
+ */
+ public static ObjectMapper findAnyObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
+ ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
+ if (mapper != null) return mapper;
+
+ if (mgmt == null && servletContext != null) {
+ mgmt = getManagementContext(servletContext);
+ }
+ return newPrivateObjectMapper(mgmt);
+ }
+
+ /**
+ * @return A new Brooklyn-specific ObjectMapper.
+ * Normally {@link #findSharedObjectMapper(ServletContext, ManagementContext)} is preferred
+ */
+ public static ObjectMapper newPrivateObjectMapper(ManagementContext mgmt) {
+ if (mgmt == null) {
+ throw new IllegalStateException("No management context available for creating ObjectMapper");
+ }
+
+ SerializationConfig defaultConfig = new ObjectMapper().getSerializationConfig();
+ SerializationConfig sc = new SerializationConfig(
+ defaultConfig.getClassIntrospector() /* ObjectMapper.DEFAULT_INTROSPECTOR */,
+ defaultConfig.getAnnotationIntrospector() /* ObjectMapper.DEFAULT_ANNOTATION_INTROSPECTOR */,
+ new PossiblyStrictPreferringFieldsVisibilityChecker(),
+ null, null, TypeFactory.defaultInstance(), null);
+
+ ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider();
+ sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer());
+
+ ObjectMapper mapper = new ObjectMapper(null, sp, null, sc, null);
+ SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
+
+ new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule);
+ new BidiSerialization.EntitySerialization(mgmt).install(mapperModule);
+ new BidiSerialization.LocationSerialization(mgmt).install(mapperModule);
+ mapperModule.addSerializer(new MultimapSerializer());
+
+ mapper.registerModule(mapperModule);
+ return mapper;
+ }
+
+ public static ManagementContext getManagementContext(ServletContext servletContext) {
+ return OsgiCompat.getManagementContext(servletContext);
+ }
+
+ @Override
+ public void setManagementContext(ManagementContext mgmt) {
+ this.mgmt = mgmt;
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
----------------------------------------------------------------------
diff --cc brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
index 0000000,9da223e..483f04d
mode 000000,100644..100644
--- a/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
+++ b/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
@@@ -1,0 -1,359 +1,359 @@@
+
+ # this catalog bom is an illustration supplying a few useful sample items
+ # and templates to get started using Brooklyn
+
+ brooklyn.catalog:
+ version: 0.9.0-SNAPSHOT # BROOKLYN_VERSION
+ items:
+
+ # load everything in the classpath with a @Catalog annotation
+ - scanJavaAnnotations: true
+
+ - id: server
+ description: |
+ Provision a server, with customizable provisioning.properties and credentials installed,
+ but no other special software process or scripts executed.
+ item:
+ type: org.apache.brooklyn.entity.software.base.EmptySoftwareProcess
+ name: Server
+
+ - id: vanilla-bash-server
+ description: |
+ Provision a server, with customizable provisioning.properties and credentials installed,
+ but no other special software process or scripts executed.
+ The script should be supplied in "launch.command" as per docs on
+ org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess.
+ item:
+ type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess
+ name: Server with Launch Script (bash)
+
+ - id: load-balancer
+ description: |
+ Create a load balancer which will point at members in the group entity
+ referred to by the config key "serverPool".
+ The sensor advertising the port can be configured with the "member.sensor.portNumber" config key,
+ defaulting to `http.port`; all member entities which have published "service.up" will then be picked up.
+ item:
+ type: org.apache.brooklyn.entity.proxy.nginx.NginxController
+ name: Load Balancer (nginx)
+
+ - id: cluster
+ description: |
+ Create a cluster of entities, resizable, with starting size "initialSize",
+ and using a spec supplied in the "memberSpec" key.
+ item:
+ type: org.apache.brooklyn.entity.group.DynamicCluster
+
+ - id: 1-server-template
+ itemType: template
+ name: "Template 1: Server"
+ description: |
+ Sample YAML to provision a server in a cloud with illustrative VM properties
+ item:
+ name: Server (Brooklyn Example)
+
+ # this basic example shows how Brooklyn can provision a single raw VM
+ # in the cloud or location of your choice
+
+ services:
+ - type: server
+ name: My VM
+
+ # location can be e.g. `softlayer` or `jclouds:openstack-nova:https://9.9.9.9:9999/v2.0/`,
- # or `localhost` or `byon: { nodes: [ 10.0.0.1, 10.0.0.2, 10.0.1.{1,2} ] }`
++ # or `localhost` or `byon:(hosts="10.9.1.1,10.9.1.2,produser2@10.9.2.{10,11,20-29}")`
+ location:
+ jclouds:aws-ec2:
+ # edit these to use your credential (or delete if credentials specified in brooklyn.properties)
+ identity: <REPLACE>
+ credential: <REPLACE>
+
+ region: eu-central-1
+
+ # we want Ubuntu, with a lot of RAM
+ osFamily: ubuntu
+ minRam: 8gb
+
+ # set up this user and password (default is to authorize a public key)
+ user: sample
+ password: s4mpl3
+
+ - id: 2-bash-web-server-template
+ itemType: template
+ name: "Template 2: Bash Web Server"
+ description: |
+ Sample YAML building on Template 1,
+ adding bash commands to launch a Python-based web server
+ on port 8020
+ item:
+ name: Python Web Server (Brooklyn Example)
+
+ # this example builds on the previous one,
+ # adding some scripts to initialize the VM
+
+ services:
+ - type: vanilla-bash-server
+ name: My Bash Web Server VM
+ brooklyn.config:
+ install.command: |
+ # install python if not present
+ which python || \
+ { apt-get update && apt-get install python ; } || \
+ { yum update && yum install python ; } || \
+ { echo WARNING: cannot install python && exit 1 ; }
+
+ customize.command: |
+ # create the web page to serve
+ cat > index.html << EOF
+
+ Hello world.
+ <p>
+ I am ${ENTITY_INFO}, ${MESSAGE:-a Brooklyn sample}.
+ <p>
+ Created at: `date`
+ <p>
+ I am running at ${HOSTNAME}, with on-box IP configuration:
+ <pre>
+ `ifconfig | grep inet`
+ </pre>
+
+ EOF
+
+ launch.command: |
+ # launch in background (ensuring no streams open), and record PID to file
+ nohup python -m SimpleHTTPServer ${PORT:-8020} < /dev/null > output.txt 2>&1 &
+ echo $! > ${PID_FILE:-pid.txt}
+ sleep 5
+ ps -p `cat ${PID_FILE:-pid.txt}`
+ if [ $? -ne 0 ] ; then
+ cat output.txt
+ echo WARNING: python web server not running
+ exit 1
+ fi
+
+ shell.env:
+ HOSTNAME: $brooklyn:attributeWhenReady("host.name")
+ PORT: $brooklyn:config("my.app.port")
+ ENTITY_INFO: $brooklyn:component("this", "")
+ MESSAGE: $brooklyn:config("my.message")
+
+ # custom
+ my.app.port: 8020
+ my.message: "good to meet you"
+
+ brooklyn.enrichers:
+ # publish the URL as a sensor; the GUI will pick this up (main.uri)
+ - type: org.apache.brooklyn.enricher.stock.Transformer
+ brooklyn.config:
+ uniqueTag: url-generator
+ enricher.sourceSensor: host.subnet.hostname
+ # use the definition from Attributes class, as it has a RendererHint so GUI makes it a link
+ enricher.targetSensor: $brooklyn:sensor("org.apache.brooklyn.core.entity.Attributes", "main.uri")
+ enricher.targetValue:
+ $brooklyn:formatString:
+ - "http://%s:%s/"
+ - $brooklyn:attributeWhenReady("host.subnet.hostname")
+ - $brooklyn:config("my.app.port")
+
+ location:
+ jclouds:aws-ec2:
+ region: eu-central-1
+ # edit these (or delete if credentials specified in brooklyn.properties)
+ identity: <REPLACE>
+ credential: <REPLACE>
+
+ - id: 3-bash-web-and-riak-template
+ itemType: template
+ name: "Template 3: Bash Web Server and Scaling Riak Cluster"
+ description: |
+ Sample YAML building on Template 2,
+ composing that blueprint with a Riak cluster and injecting the URL
+ item:
+ name: Bash Web Server and Riak Cluster (Brooklyn Example)
+
+ # this example *references* the previous one,
+ # combining it with a stock blueprint for a Riak cluster,
+ # and shows how a sensor from the latter can be injected
+
+ services:
+
+ # reference template 2, overriding message to point at riak
+ - type: 2-bash-web-server-template
+ brooklyn.config:
+ my.message: $brooklyn:formatString("connected to Riak at %s",
+ $brooklyn:entity("riak-cluster").attributeWhenReady("main.uri"))
+
+ # use the off-the-shelf Riak cluster
+ - type: org.apache.brooklyn.entity.nosql.riak.RiakCluster
+ id: riak-cluster
+ initialSize: 3
+ # and add a policy to scale based on ops per minute
+ brooklyn.policies:
+ - type: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+ brooklyn.config:
+ metric: riak.node.ops.1m.perNode
+ # more than 100 ops per second (6k/min) scales out, less than 50 scales back
+ # up to a max of 8 riak nodes here (can be changed in GUI / REST API afterwards)
+ metricLowerBound: 3000
+ metricUpperBound: 6000
+ minPoolSize: 3
+ maxPoolSize: 8
+ resizeUpStabilizationDelay: 30s
+ resizeDownStabilizationDelay: 5m
+
+ location:
+ jclouds:aws-ec2:
+ region: eu-central-1
+ # edit these (or delete if credentials specified in brooklyn.properties)
+ identity: <REPLACE>
+ credential: <REPLACE>
+
+ - id: 4-resilient-bash-web-cluster-template
+ itemType: template
+ name: "Template 4: Resilient Load-Balanced Bash Web Cluster with Sensors"
+ description: |
+ Sample YAML to provision a cluster of the bash/python web server nodes,
+ with sensors configured, and a load balancer pointing at them,
+ and resilience policies for node replacement and scaling
+ item:
+ name: Resilient Load-Balanced Bash Web Cluster (Brooklyn Example)
+
+ # this final example shows some of the advanced functionality:
+ # defining custom sensors, and a cluster with a "spec",
+ # policies for resilience and scaling based on that sensor,
+ # and wiring a load balancer in front of the cluster
+
+ # combining this with the riak cluster in the previous example
+ # is left as a suggested exercise for the user
+
+ services:
+
+ # define a cluster of the web nodes
+ - type: cluster
+ name: Cluster of Bash Web Nodes
+ id: my-web-cluster
+ brooklyn.config:
+ initialSize: 1
+ memberSpec:
+ $brooklyn:entitySpec:
+ # template 2 is used as the spec for items in this cluster
+ # with a new message overwriting the previous,
+ # and a lot of sensors defined
+ type: 2-bash-web-server-template
+ name: My Bash Web Server VM with Sensors
+
+ brooklyn.config:
+ my.message: "part of the cluster"
+
+ brooklyn.initializers:
+ # make a simple request-count sensor, by counting the number of 200 responses in output.txt
+ - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor
+ brooklyn.config:
+ name: reqs.count
+ targetType: int
+ period: 5s
+ command: "cat output.txt | grep HTTP | grep 200 | wc | awk '{print $1}'"
+ # and publish the port as a sensor so the load-balancer can pick it up
+ - type: org.apache.brooklyn.core.sensor.StaticSensor
+ brooklyn.config:
+ name: app.port
+ targetType: int
+ static.value: $brooklyn:config("my.app.port")
+
+ brooklyn.enrichers:
+ # derive reqs.per_sec from reqs.count
+ - type: org.apache.brooklyn.enricher.stock.YamlTimeWeightedDeltaEnricher
+ brooklyn.config:
+ enricher.sourceSensor: reqs.count
+ enricher.targetSensor: reqs.per_sec
+ enricher.delta.period: 1s
+ # and take an average over 30s for reqs.per_sec into reqs.per_sec.windowed_30s
+ - type: org.apache.brooklyn.enricher.stock.YamlRollingTimeWindowMeanEnricher
+ brooklyn.config:
+ enricher.sourceSensor: reqs.per_sec
+ enricher.targetSensor: reqs.per_sec.windowed_30s
+ enricher.window.duration: 30s
+
+ # emit failure sensor if a failure connecting to the service is sustained for 30s
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
+ brooklyn.config:
+ entityFailed.stabilizationDelay: 30s
+
+ brooklyn.policies:
+ # restart if a failure is detected (with a max of one restart in 2m, sensor will propagate otherwise)
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
+ brooklyn.config:
+ failOnRecurringFailuresInThisDuration: 2m
+
+ # back at the cluster, create a total per-sec and some per-node average
+ brooklyn.enrichers:
+ - type: org.apache.brooklyn.enricher.stock.Aggregator
+ brooklyn.config:
+ enricher.sourceSensor: reqs.per_sec
+ enricher.targetSensor: reqs.per_sec
+ transformation: sum
+ - type: org.apache.brooklyn.enricher.stock.Aggregator
+ brooklyn.config:
+ enricher.sourceSensor: reqs.per_sec
+ enricher.targetSensor: reqs.per_sec.per_node
+ transformation: average
+ - type: org.apache.brooklyn.enricher.stock.Aggregator
+ brooklyn.config:
+ enricher.sourceSensor: reqs.per_sec.windowed_30s
+ enricher.targetSensor: reqs.per_sec.windowed_30s.per_node
+ transformation: average
+
+ brooklyn.policies:
+ # resilience: if a per-node restart policy fails,
+ # just throw that node away and create a new one
+ - type: org.apache.brooklyn.policy.ha.ServiceReplacer
+
+ # and scale based on reqs/sec
+ - type: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+ brooklyn.config:
+ # scale based on reqs/sec (though in a real-world situation,
+ # reqs.per_sec.windowed_30s.per_node might be a better choice)
+ metric: reqs.per_sec.per_node
+
+ # really low numbers, so you can trigger a scale-out just by hitting reload a lot
+ metricUpperBound: 3
+ metricLowerBound: 1
+
+ # sustain 3 reqs/sec for 2s and it will scale out
+ resizeUpStabilizationDelay: 2s
+ # only scale down when sustained for 1m
+ resizeDownStabilizationDelay: 1m
+
+ maxPoolSize: 10
+
+ # and add a load-balancer pointing at the cluster
+ - type: load-balancer
+ id: load-bal
+ brooklyn.config:
+ # point this load balancer at the cluster, specifying port to forward to
+ loadbalancer.serverpool: $brooklyn:entity("my-web-cluster")
+ member.sensor.portNumber: app.port
+
+ brooklyn.enrichers:
+ # publish a few useful info sensors and KPI's to the root of the app
+ - type: org.apache.brooklyn.enricher.stock.Propagator
+ brooklyn.config:
+ uniqueTag: propagate-load-balancer-url
+ producer: $brooklyn:entity("load-bal")
+ propagating:
+ - main.uri
+ - type: org.apache.brooklyn.enricher.stock.Propagator
+ brooklyn.config:
+ uniqueTag: propagate-reqs-per-sec
+ producer: $brooklyn:entity("my-web-cluster")
+ propagating:
+ - reqs.per_sec
+ - reqs.per_sec.windowed_30s.per_node
+
+ location:
+ jclouds:aws-ec2:
+ # edit these (or delete if credentials specified in brooklyn.properties)
+ identity: <REPLACE>
+ credential: <REPLACE>
+
+ region: eu-central-1
+ minRam: 2gb
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/LocalBrooklynNodeImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/LocalBrooklynNodeImpl.java
index 0000000,7664d2b..8d95a39
mode 000000,100644..100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/LocalBrooklynNodeImpl.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/LocalBrooklynNodeImpl.java
@@@ -1,0 -1,46 +1,48 @@@
+ /*
+ * 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.brooklynnode;
+
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
++import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+ import org.apache.brooklyn.util.text.Strings;
+
+ public class LocalBrooklynNodeImpl extends BrooklynNodeImpl implements LocalBrooklynNode {
+
+ private static final String LOCAL_BROOKLYN_NODE_KEY = "brooklyn.entity.brooklynnode.local.%s";
+ private static final String BROOKLYN_WEBCONSOLE_PASSWORD_KEY = "brooklyn.webconsole.security.user.%s.password";
+
+ @Override
+ protected void connectSensors() {
+ // Override management username and password from brooklyn.properties
- BrooklynProperties properties = (BrooklynProperties) getManagementContext().getConfig();
++ // TODO Why use BrooklynProperties, rather than StringConfigMap returned by mgmt.getConfig()?
++ BrooklynProperties properties = ((ManagementContextInternal)getManagementContext()).getBrooklynProperties();
+ String user = (String) properties.get(String.format(LOCAL_BROOKLYN_NODE_KEY, "user"));
+ String password = (String) properties.get(String.format(LOCAL_BROOKLYN_NODE_KEY, "password"));
+ if (Strings.isBlank(password)) {
+ if (Strings.isBlank(user)) user = "admin";
+ password = (String) properties.get(String.format(BROOKLYN_WEBCONSOLE_PASSWORD_KEY, user));
+ }
+ if (Strings.isNonBlank(user) && Strings.isNonBlank(password)) {
+ config().set(MANAGEMENT_USER, user);
+ config().set(MANAGEMENT_PASSWORD, password);
+ }
+ super.connectSensors();
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineEntityImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineEntityImpl.java
index 0000000,881dd7b..87bbc72
mode 000000,100644..100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineEntityImpl.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/machine/MachineEntityImpl.java
@@@ -1,0 -1,182 +1,186 @@@
+ /*
+ * 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.machine;
+
+ import java.util.List;
+ import java.util.concurrent.TimeoutException;
+
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
+ import org.apache.brooklyn.core.location.Machines;
+ import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+ import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
+ import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+ import org.apache.brooklyn.feed.ssh.SshFeed;
+ import org.apache.brooklyn.feed.ssh.SshPollConfig;
+ import org.apache.brooklyn.feed.ssh.SshPollValue;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.util.core.task.DynamicTasks;
+ import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Duration;
+
+ import com.google.common.base.Function;
+ import com.google.common.base.Functions;
+ import com.google.common.base.Splitter;
+
+ public class MachineEntityImpl extends EmptySoftwareProcessImpl implements MachineEntity {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MachineEntityImpl.class);
+
+ static {
+ MachineAttributes.init();
+ }
+
+ private transient SshFeed sensorFeed;
+
+ @Override
+ public void init() {
+ LOG.info("Starting server pool machine with id {}", getId());
+ super.init();
+ }
+
+ @Override
+ protected void connectSensors() {
+ super.connectSensors();
+
+ // Sensors linux-specific
+ if (!getMachine().getMachineDetails().getOsDetails().isLinux()) return;
+
+ sensorFeed = SshFeed.builder()
+ .entity(this)
+ .period(Duration.THIRTY_SECONDS)
+ .poll(new SshPollConfig<Duration>(UPTIME)
+ .command("cat /proc/uptime")
+ .onFailureOrException(Functions.<Duration>constant(null))
+ .onSuccess(new Function<SshPollValue, Duration>() {
+ @Override
+ public Duration apply(SshPollValue input) {
+ return Duration.seconds( Double.valueOf( Strings.getFirstWord(input.getStdout()) ) );
+ }
+ }))
+ .poll(new SshPollConfig<Double>(LOAD_AVERAGE)
+ .command("uptime")
+ .onFailureOrException(Functions.constant(-1d))
+ .onSuccess(new Function<SshPollValue, Double>() {
+ @Override
+ public Double apply(SshPollValue input) {
+ String loadAverage = Strings.getFirstWordAfter(input.getStdout(), "load average:").replace(",", "");
+ return Double.valueOf(loadAverage);
+ }
+ }))
+ .poll(new SshPollConfig<Double>(CPU_USAGE)
+ .command("cat /proc/stat")
+ .onFailureOrException(Functions.constant(-1d))
+ .onSuccess(new Function<SshPollValue, Double>() {
+ @Override
+ public Double apply(SshPollValue input) {
+ List<String> cpuData = Splitter.on(" ").omitEmptyStrings().splitToList(Strings.getFirstLine(input.getStdout()));
+ Integer system = Integer.parseInt(cpuData.get(1));
+ Integer user = Integer.parseInt(cpuData.get(3));
+ Integer idle = Integer.parseInt(cpuData.get(4));
+ return (double) (system + user) / (double) (system + user + idle);
+ }
+ }))
+ .poll(new SshPollConfig<Long>(USED_MEMORY)
+ .command("free | grep Mem:")
+ .onFailureOrException(Functions.constant(-1L))
+ .onSuccess(new Function<SshPollValue, Long>() {
+ @Override
+ public Long apply(SshPollValue input) {
+ List<String> memoryData = Splitter.on(" ").omitEmptyStrings().splitToList(Strings.getFirstLine(input.getStdout()));
+ return Long.parseLong(memoryData.get(2));
+ }
+ }))
+ .poll(new SshPollConfig<Long>(FREE_MEMORY)
+ .command("free | grep Mem:")
+ .onFailureOrException(Functions.constant(-1L))
+ .onSuccess(new Function<SshPollValue, Long>() {
+ @Override
+ public Long apply(SshPollValue input) {
+ List<String> memoryData = Splitter.on(" ").omitEmptyStrings().splitToList(Strings.getFirstLine(input.getStdout()));
+ return Long.parseLong(memoryData.get(3));
+ }
+ }))
+ .poll(new SshPollConfig<Long>(TOTAL_MEMORY)
+ .command("free | grep Mem:")
+ .onFailureOrException(Functions.constant(-1L))
+ .onSuccess(new Function<SshPollValue, Long>() {
+ @Override
+ public Long apply(SshPollValue input) {
+ List<String> memoryData = Splitter.on(" ").omitEmptyStrings().splitToList(Strings.getFirstLine(input.getStdout()));
+ return Long.parseLong(memoryData.get(1));
+ }
+ }))
+ .build();
+
+ }
+
+ @Override
+ public void disconnectSensors() {
+ if (sensorFeed != null) sensorFeed.stop();
+ super.disconnectSensors();
+ }
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return EmptySoftwareProcessDriver.class;
+ }
+
+ public SshMachineLocation getMachine() {
+ return Machines.findUniqueMachineLocation(getLocations(), SshMachineLocation.class).get();
+ }
+
+ @Override
+ public String execCommand(String command) {
+ return execCommandTimeout(command, Duration.ONE_MINUTE);
+ }
+
+ @Override
+ public String execCommandTimeout(String command, Duration timeout) {
++ AbstractSoftwareProcessSshDriver driver = (AbstractSoftwareProcessSshDriver) getDriver();
++ if (driver == null) {
++ throw new NullPointerException("No driver for "+this);
++ }
+ ProcessTaskWrapper<String> task = SshEffectorTasks.ssh(command)
- .environmentVariables(((AbstractSoftwareProcessSshDriver) getDriver()).getShellEnvironment())
++ .environmentVariables(driver.getShellEnvironment())
+ .requiringZeroAndReturningStdout()
+ .machine(getMachine())
+ .summary(command)
+ .newTask();
+
+ try {
+ String result = DynamicTasks.queueIfPossible(task)
+ .executionContext(this)
+ .orSubmitAsync()
+ .asTask()
+ .get(timeout);
+ return result;
+ } catch (TimeoutException te) {
+ throw new IllegalStateException("Timed out running command: " + command);
+ } catch (Exception e) {
+ Integer exitCode = task.getExitCode();
+ LOG.warn("Command failed, return code {}: {}", exitCode == null ? -1 : exitCode, task.getStderr());
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
index 0000000,ad16403..884da19
mode 000000,100644..100644
--- a/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
+++ b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java
@@@ -1,0 -1,610 +1,609 @@@
+ /*
+ * 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.software.base.test.location;
+
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertNotEquals;
+
+ import java.io.ByteArrayInputStream;
+ import java.io.File;
+ import java.lang.reflect.Method;
+ import java.util.Arrays;
+ import java.util.List;
+ import java.util.concurrent.Callable;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+
+ import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+ import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+ import org.apache.brooklyn.core.test.entity.TestApplication;
+ import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+ import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse;
+ import org.apache.brooklyn.util.text.Identifiers;
+ import org.apache.brooklyn.util.time.Time;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.annotations.AfterClass;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
+
-import com.google.api.client.util.Charsets;
++import com.google.common.base.Charsets;
+ import com.google.common.base.Joiner;
+ import com.google.common.base.Stopwatch;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Lists;
+ import com.google.common.io.Files;
+ import com.google.common.util.concurrent.Futures;
+ import com.google.common.util.concurrent.ListenableFuture;
+ import com.google.common.util.concurrent.ListeningExecutorService;
+ import com.google.common.util.concurrent.MoreExecutors;
+
+ /**
+ * Tests execution of commands (batch and powershell) on Windows over WinRM, and of
+ * file upload.
+ *
+ * There are limitations with what is supported by PyWinRM. These are highlighted in
+ * tests marked as "WIP" (see individual tests).
+ *
+ * These limitations are documented in docs/guide/yaml/winrm/index.md.
+ * Please update the docs if you encountered new situations, or change the behaviuor
+ * of existing use-cases.
+ */
+
+ public class WinRmMachineLocationLiveTest {
+ private static final int MAX_EXECUTOR_THREADS = 100;
+
+ /*
+ * TODO: Deferred implementing copyFrom or environment variables.
+ */
+
+ private static final Logger LOG = LoggerFactory.getLogger(WinRmMachineLocationLiveTest.class);
+
+ private static final String INVALID_CMD = "thisCommandDoesNotExistAEFafiee3d";
+ private static final String PS_ERR_ACTION_PREF_EQ_STOP = "$ErrorActionPreference = \"Stop\"";
+
+ protected MachineProvisioningLocation<WinRmMachineLocation> loc;
+ protected TestApplication app;
+ protected ManagementContextInternal mgmt;
+
+ protected WinRmMachineLocation machine;
+
+ private ListeningExecutorService executor;
+
+ @BeforeClass(alwaysRun=true)
+ public void setUpClass() throws Exception {
+ executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(MAX_EXECUTOR_THREADS));
+
+ mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+
+ loc = newLoc(mgmt);
+ machine = loc.obtain(ImmutableMap.of());
+
+ LOG.info("PROVISIONED: "+machine.getAddress()+":"+machine.config().get(WinRmMachineLocation.WINRM_PORT)
+ +", "+machine.getUser()+":"+machine.config().get(WinRmMachineLocation.PASSWORD));
+ }
+
+ @AfterClass(alwaysRun=true)
+ public void tearDownClass() throws Exception {
+ try {
+ if (executor != null) executor.shutdownNow();
+ if (machine != null) loc.release(machine);
+ if (mgmt != null) Entities.destroyAll(mgmt);
+ } catch (Throwable t) {
+ LOG.error("Caught exception in tearDown method", t);
+ } finally {
+ executor = null;
+ mgmt = null;
+ }
+ }
+
+ /**
+ * Returns a location for obtaining a single WinRM machine. This method will be called once during
+ * {@code @BeforeClass}, then {@code loc.obtain()} will be called. The obtained machine will be
+ * released in {@code @AfterClass}.
+ */
+ protected MachineProvisioningLocation<WinRmMachineLocation> newLoc(ManagementContextInternal mgmt) throws Exception {
+ return WindowsTestFixture.setUpWindowsLocation(mgmt);
+ }
+
+ @Test(groups="Live")
+ public void testCopyTo() throws Exception {
+ String contents = "abcdef";
+ runCopyTo(contents);
+ runCopyFileTo(contents);
+ }
+
+ // Takes several minutes to upload/download!
+ @Test(groups="Live")
+ public void testLargeFileCopyTo() throws Exception {
+ String contents = Identifiers.makeRandomId(65537);
+ runCopyTo(contents);
+ runCopyFileTo(contents);
+ }
+
+ protected void runCopyTo(String contents) throws Exception {
+ String remotePath = "C:\\myfile-"+Identifiers.makeRandomId(4)+".txt";
+ machine.copyTo(new ByteArrayInputStream(contents.getBytes()), remotePath);
+
+ WinRmToolResponse response = machine.executeScript("type "+remotePath);
+ String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+ assertEquals(response.getStatusCode(), 0, msg);
+ assertEquals(response.getStdOut().trim(), contents, msg);
+ }
+
+ protected void runCopyFileTo(String contents) throws Exception {
+ File localFile = File.createTempFile("winrmtest"+Identifiers.makeRandomId(4), ".txt");
+ try {
+ Files.write(contents, localFile, Charsets.UTF_8);
+ String remotePath = "C:\\myfile-"+Identifiers.makeRandomId(4)+".txt";
+ machine.copyTo(localFile, remotePath);
+
+ WinRmToolResponse response = machine.executeScript("type "+remotePath);
+ String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+ assertEquals(response.getStatusCode(), 0, msg);
+ assertEquals(response.getStdOut().trim(), contents, msg);
+ } finally {
+ localFile.delete();
+ }
+ }
+
+ @Test(groups="Live")
+ public void testExecScript() throws Exception {
+ assertExecSucceeds("echo myline", "myline", "");
+ }
+
+ /*
+ * TODO Not supported in PyWinRM.
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_cmd("echo first \r\n echo second")
+ * gives just "first".
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecMultiLineScript() throws Exception {
+ assertExecSucceeds("echo first" + "\r\n" + "echo second", "first"+"\r\n"+"second", "");
+ }
+
+ /*
+ * TODO Not supported in PyWinRM. Under the covers, we just concatenate the commands.
+ * See {@link #testExecMultiLineScript()}.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecMultiPartScript() throws Exception {
+ assertExecSucceeds(ImmutableList.of("echo first", "echo second"), "first"+"\r\n"+"second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecFailingScript() throws Exception {
+ final String INVALID_CMD = "thisCommandDoesNotExistAEFafiee3d";
+
+ // Single commands
+ assertExecFails(INVALID_CMD);
+ assertExecFails(ImmutableList.of(INVALID_CMD));
+ }
+
+ @Test(groups="Live")
+ public void testExecScriptExit0() throws Exception {
+ assertExecSucceeds("exit /B 0", "", "");
+ assertExecSucceeds(ImmutableList.of("exit /B 0"), "", "");
+ }
+
+ /*
+ * TODO Not supported in PyWinRM.
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_cmd("exit /B 1")
+ * gives exit code 0.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecScriptExit1() throws Exception {
+ // Single commands
+ assertExecFails("exit /B 1");
+ assertExecFails(ImmutableList.of("exit /B 1"));
+ }
+
+ @Test(groups="Live")
+ public void testExecBatchFileSingleLine() throws Exception {
+ String script = "EXIT /B 0";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecSucceeds(scriptPath, null, "");
+ }
+
+ @Test(groups="Live")
+ public void testExecBatchFileMultiLine() throws Exception {
+ String script = Joiner.on("\n").join(
+ "@ECHO OFF",
+ "echo first",
+ "echo second",
+ "EXIT /B 0");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecSucceeds(scriptPath, "first"+"\r\n"+"second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecBatchFileWithArgs() throws Exception {
+ String script = Joiner.on("\n").join(
+ "@ECHO OFF",
+ "echo got %1",
+ "echo got %2",
+ "EXIT /B 0");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecSucceeds(scriptPath+" first second", "got first"+"\r\n"+"got second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecBatchFileWithExit1() throws Exception {
+ String script = "EXIT /B 1";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecFails(scriptPath);
+ }
+
+ @Test(groups="Live")
+ public void testExecCorruptExe() throws Exception {
+ String exe = "garbage";
+ String exePath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".exe";
+ machine.copyTo(new ByteArrayInputStream(exe.getBytes()), exePath);
+
+ assertExecFails(exePath);
+ }
+
+ @Test(groups="Live")
+ public void testExecFilePs() throws Exception {
+ String script = Joiner.on("\r\n").join(
+ "Write-Host myline",
+ "exit 0");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsSucceeds(
+ "PowerShell -NonInteractive -NoProfile -Command "+scriptPath,
+ "myline",
+ "");
+ }
+
+ @Test(groups="Live")
+ public void testExecFilePsWithExit1() throws Exception {
+ String script = Joiner.on("\r\n").join(
+ "Write-Host myline",
+ "exit 1");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecFails("PowerShell -NonInteractive -NoProfile -Command "+scriptPath);
+ }
+
+ /*
+ * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+ * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineExit1.ps1")
+ * gives exit code 0.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecFilePsWithSingleLineExit1() throws Exception {
+ String script = "exit 1";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecFails("PowerShell -NonInteractive -NoProfile -Command "+scriptPath);
+ }
+
+ @Test(groups="Live")
+ public void testExecPsScript() throws Exception {
+ assertExecPsSucceeds("Write-Host myline", "myline", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsMultiLineScript() throws Exception {
+ // Note stdout is "\n" rather than "\r\n" (the latter is returned for run_cmd, versus run_ps)
+ assertExecPsSucceeds("Write-Host first" + "\r\n" + "Write-Host second", "first"+"\n"+"second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsMultiLineScriptWithoutSlashR() throws Exception {
+ assertExecPsSucceeds("Write-Host first" + "\n" + "Write-Host second", "first"+"\n"+"second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsMultiPartScript() throws Exception {
+ assertExecPsSucceeds(ImmutableList.of("Write-Host first", "Write-Host second"), "first"+"\n"+"second", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsBatchFile() throws Exception {
+ String script = "EXIT /B 0";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsSucceeds("& '"+scriptPath+"'", null, "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsBatchFileExit1() throws Exception {
+ String script = "EXIT /B 1";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsFails("& '"+scriptPath+"'");
+ }
+
+ /*
+ * TODO Not supported in PyWinRM - gives exit status 1, rather than the 3 from the batch file.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecPsBatchFileExit3() throws Exception {
+ String script = "EXIT /B 3";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".bat";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ WinRmToolResponse response = machine.executePsScript("& '"+scriptPath+"'");
+ String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+ assertEquals(response.getStatusCode(), 3, msg);
+ }
+
+ @Test(groups="Live")
+ public void testExecPsCorruptExe() throws Exception {
+ String exe = "garbage";
+ String exePath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".exe";
+ machine.copyTo(new ByteArrayInputStream(exe.getBytes()), exePath);
+
+ assertExecPsFails("& '"+exePath+"'");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsFileWithArg() throws Exception {
+ String script = Joiner.on("\r\n").join(
+ "Param(",
+ " [string]$myarg",
+ ")",
+ "Write-Host got $myarg",
+ "exit 0");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsSucceeds("& "+scriptPath+" -myarg myval", "got myval", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsFilePs() throws Exception {
+ String script = Joiner.on("\r\n").join(
+ "Write-Host myline",
+ "exit 0");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsSucceeds("& "+scriptPath, "myline", "");
+ }
+
+ @Test(groups="Live")
+ public void testExecPsFilePsWithExit1() throws Exception {
+ String script = Joiner.on("\r\n").join(
+ "Write-Host myline",
+ "exit 1");
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+ System.out.println(scriptPath);
+
+ assertExecPsFails("& "+scriptPath);
+ }
+
+ /*
+ * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+ * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineExit1.ps1")
+ * gives exit code 0.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecPsFilePsSingleLineWithExit1() throws Exception {
+ String script = "exit 1";
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsFails("& "+scriptPath);
+ }
+
+ /*
+ * TODO Not supported in PyWinRM - single line .ps1 file with "exit 1" gives an
+ * exit code 0 over PyWinRM, but an exit code 1 when executed locally!
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_cmd("PowerShell -NonInteractive -NoProfile -Command C:\singleLineGarbage.ps1")
+ * gives exit code 0.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecPsFilePsSingleLineWithInvalidCommand() throws Exception {
+ String script = INVALID_CMD;
+ String scriptPath = "C:\\myscript-"+Identifiers.makeRandomId(4)+".ps1";
+ machine.copyTo(new ByteArrayInputStream(script.getBytes()), scriptPath);
+
+ assertExecPsFails("& "+scriptPath);
+ }
+
+ @Test(groups="Live")
+ public void testConfirmUseOfErrorActionPreferenceDoesNotCauseErr() throws Exception {
+ // Confirm that ErrorActionPreference=Stop does not itself cause a failure, and still get output on success.
+ assertExecPsSucceeds(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline"), "myline", "");
+ }
+
+ /*
+ * TODO Not supported in PyWinRM
+ *
+ * Executing (in python):
+ * import winrm
+ * s = winrm.Session('52.12.211.247', auth=('Administrator', 'pa55w0rd'))
+ * r = s.run_ps("exit 1")
+ * gives exit code 0.
+ */
+ @Test(groups={"Live", "WIP"})
+ public void testExecPsExit1() throws Exception {
+ // Single commands
+ assertExecPsFails("exit 1");
+ assertExecPsFails(ImmutableList.of("exit 1"));
+
+ // Multi-part
+ assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline", "exit 1"));
+
+ // Multi-line
+ assertExecPsFails(PS_ERR_ACTION_PREF_EQ_STOP + "\n" + "Write-Host myline" + "\n" + "exit 1");
+ }
+
+ @Test(groups="Live")
+ public void testExecFailingPsScript() throws Exception {
+ // Single commands
+ assertExecPsFails(INVALID_CMD);
+ assertExecPsFails(ImmutableList.of(INVALID_CMD));
+
+ // Multi-part commands
+ assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, "Write-Host myline", INVALID_CMD));
+ assertExecPsFails(ImmutableList.of(PS_ERR_ACTION_PREF_EQ_STOP, INVALID_CMD, "Write-Host myline"));
+ }
+
+ @Test(groups={"Live", "Acceptance"})
+ public void testExecConcurrently() throws Exception {
+ final int NUM_RUNS = 10;
+ final int TIMEOUT_MINS = 30;
+ final AtomicInteger counter = new AtomicInteger();
+
+ // Find the test methods that are enabled, and that are not WIP
+ List<Method> methodsToRun = Lists.newArrayList();
+ Method[] allmethods = WinRmMachineLocationLiveTest.class.getMethods();
+ for (Method method : allmethods) {
+ Test annotatn = method.getAnnotation(Test.class);
+ if (method.getParameterTypes().length != 0) {
+ continue;
+ }
+ if (method.getName().equals("testExecConcurrently")) {
+ continue;
+ }
+ if (annotatn == null || !annotatn.enabled()) {
+ continue;
+ }
+ String[] groups = annotatn.groups();
+ if (groups != null && Arrays.asList(groups).contains("WIP")) {
+ continue;
+ }
+ methodsToRun.add(method);
+ }
+
+ // Execute all the methods many times
+ LOG.info("Executing "+methodsToRun.size()+" methods "+NUM_RUNS+" times each, with "+MAX_EXECUTOR_THREADS+" threads for concurrent execution; max permitted time "+TIMEOUT_MINS+"mins; methods="+methodsToRun);
+
+ List<ListenableFuture<?>> results = Lists.newArrayList();
+ for (int i = 0; i < NUM_RUNS; i++) {
+ for (final Method method : methodsToRun) {
+ results.add(executor.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ LOG.info("Executing "+method.getName()+" in thread "+Thread.currentThread());
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ try {
+ method.invoke(WinRmMachineLocationLiveTest.this);
+ LOG.info("Executed "+method.getName()+" in "+Time.makeTimeStringRounded(stopwatch)+", in thread "+Thread.currentThread()+"; total "+counter.incrementAndGet()+" methods done");
+ return null;
+ } catch (Exception e) {
+ LOG.error("Execute failed for "+method.getName()+" after "+Time.makeTimeStringRounded(stopwatch)+", in thread "+Thread.currentThread()+"; total "+counter.incrementAndGet()+" methods done");
+ throw e;
+ }
+ }}));
+ }
+ }
+
+ Futures.allAsList(results).get(TIMEOUT_MINS, TimeUnit.MINUTES);
+ }
+
+ private void assertExecFails(String cmd) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertFailed(cmd, machine.executeScript(cmd), stopwatch);
+ }
+
+ private void assertExecFails(List<String> cmds) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertFailed(cmds, machine.executeScript(cmds), stopwatch);
+ }
+
+ private void assertExecPsFails(String cmd) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertFailed(cmd, machine.executePsScript(cmd), stopwatch);
+ }
+
+ private void assertExecPsFails(List<String> cmds) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertFailed(cmds, machine.executePsScript(cmds), stopwatch);
+ }
+
+ private void assertExecSucceeds(String cmd, String stdout, String stderr) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertSucceeded(cmd, machine.executeScript(cmd), stdout, stderr, stopwatch);
+ }
+
+ private void assertExecSucceeds(List<String> cmds, String stdout, String stderr) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertSucceeded(cmds, machine.executeScript(cmds), stdout, stderr, stopwatch);
+ }
+
+ private void assertExecPsSucceeds(String cmd, String stdout, String stderr) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertSucceeded(cmd, machine.executePsScript(cmd), stdout, stderr, stopwatch);
+ }
+
+ private void assertExecPsSucceeds(List<String> cmds, String stdout, String stderr) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ assertSucceeded(cmds, machine.executePsScript(cmds), stdout, stderr, stopwatch);
+ }
+
+ private void assertFailed(Object cmd, WinRmToolResponse response, Stopwatch stopwatch) {
+ String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+ LOG.info("Executed in "+Time.makeTimeStringRounded(stopwatch)+" (asserting failed): "+msg+"; cmd="+cmd);
+ assertNotEquals(response.getStatusCode(), 0, msg);
+ }
+
+ private WinRmToolResponse assertSucceeded(Object cmd, WinRmToolResponse response, String stdout, String stderr, Stopwatch stopwatch) {
+ String msg = "statusCode="+response.getStatusCode()+"; out="+response.getStdOut()+"; err="+response.getStdErr();
+ LOG.info("Executed in "+Time.makeTimeStringRounded(stopwatch)+" (asserting success): "+msg+"; cmd="+cmd);
+ assertEquals(response.getStatusCode(), 0, msg);
+ if (stdout != null) assertEquals(response.getStdOut().trim(), stdout, msg);
+ if (stderr != null) assertEquals(response.getStdErr().trim(), stderr, msg);
+ return response;
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
index 0000000,f368cf5..d042991
mode 000000,100644..100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
@@@ -1,0 -1,113 +1,113 @@@
+ /*
+ * 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.test.framework;
+
-import com.google.api.client.util.Objects;
++import com.google.common.base.Objects;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Supplier;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Lists;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+
+ import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+ /**
+ * {@inheritDoc}
+ */
+ public class TestSensorImpl extends AbstractTest implements TestSensor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TestSensorImpl.class);
+
+ /**
+ * {@inheritDoc}
+ */
+ public void start(Collection<? extends Location> locations) {
+ if (!getChildren().isEmpty()) {
+ throw new RuntimeException(String.format("The entity [%s] cannot have child entities", getClass().getName()));
+ }
+ ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
+ final Entity target = resolveTarget();
+ final String sensor = getConfig(SENSOR_NAME);
+ final Duration timeout = getConfig(TIMEOUT);
+ final List<Map<String, Object>> assertions = getAssertions(this, ASSERTIONS);
+ try {
+ TestFrameworkAssertions.checkAssertions(ImmutableMap.of("timeout", timeout), assertions, sensor,
+ new Supplier<Object>() {
+ @Override
+ public Object get() {
+ final Object sensorValue = target.sensors().get(Sensors.newSensor(Object.class, sensor));
+ return sensorValue;
+ }
+ });
+
+ sensors().set(SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+ } catch (Throwable t) {
+ LOG.debug("Sensor [{}] test failed", sensor);
+ sensors().set(SERVICE_UP, false);
+ ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
+ throw Exceptions.propagate(t);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void stop() {
+ ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
+ sensors().set(SERVICE_UP, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void restart() {
+ final Collection<Location> locations = Lists.newArrayList(getLocations());
+ stop();
+ start(locations);
+ }
+
+ /**
+ * Predicate to check the equality of object
+ *
+ * @param value
+ * @return The created {@link Predicate}
+ */
+ private Predicate<Object> isEqualTo(final Object value) {
+ return new Predicate<Object>() {
+ public boolean apply(final Object input) {
+ return (input != null) && Objects.equal(TypeCoercions.coerce(value, input.getClass()), input);
+ }
+ };
+ }
+ }