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);
+             }
+         };
+     }
+ }