brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hadr...@apache.org
Subject [1/7] incubator-brooklyn git commit: [BROOKLYN-182] Move winrm-dependent code from brooklyn-core to brooklyn-software-winrm
Date Thu, 15 Oct 2015 16:16:42 GMT
Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master d53bd189c -> f380b5836


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java b/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java
new file mode 100644
index 0000000..ff659b1
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java
@@ -0,0 +1,414 @@
+/*
+ * 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.feed.windows;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.EffectorTasks;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.feed.AbstractFeed;
+import org.apache.brooklyn.core.feed.PollHandler;
+import org.apache.brooklyn.core.feed.Poller;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.feed.windows.WindowsPerformanceCounterPollConfig;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * A sensor feed that retrieves performance counters from a Windows host and posts the values to sensors.
+ *
+ * <p>To use this feed, you must provide the entity, and a collection of mappings between Windows performance counter
+ * names and Brooklyn attribute sensors.</p>
+ *
+ * <p>This feed uses SSH to invoke the windows utility <tt>typeperf</tt> to query for a specific set of performance
+ * counters, by name. The values are extracted from the response, and published to the entity's sensors.</p>
+ *
+ * <p>Example:</p>
+ *
+ * {@code
+ * @Override
+ * protected void connectSensors() {
+ *     WindowsPerformanceCounterFeed feed = WindowsPerformanceCounterFeed.builder()
+ *         .entity(entity)
+ *         .addSensor("\\Processor(_total)\\% Idle Time", CPU_IDLE_TIME)
+ *         .addSensor("\\Memory\\Available MBytes", AVAILABLE_MEMORY)
+ *         .build();
+ * }
+ * }
+ *
+ * @since 0.6.0
+ * @author richardcloudsoft
+ */
+public class WindowsPerformanceCounterFeed extends AbstractFeed {
+
+    private static final Logger log = LoggerFactory.getLogger(WindowsPerformanceCounterFeed.class);
+
+    // This pattern matches CSV line(s) with the date in the first field, and at least one further field.
+    protected static final Pattern lineWithPerfData = Pattern.compile("^\"[\\d:/\\-. ]+\",\".*\"$", Pattern.MULTILINE);
+    private static final Joiner JOINER_ON_SPACE = Joiner.on(' ');
+    private static final Joiner JOINER_ON_COMMA = Joiner.on(',');
+    private static final int OUTPUT_COLUMN_WIDTH = 100;
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Collection<WindowsPerformanceCounterPollConfig<?>>> POLLS = ConfigKeys.newConfigKey(
+            new TypeToken<Collection<WindowsPerformanceCounterPollConfig<?>>>() {},
+            "polls");
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private EntityLocal entity;
+        private Set<WindowsPerformanceCounterPollConfig<?>> polls = Sets.newLinkedHashSet();
+        private Duration period = Duration.of(30, TimeUnit.SECONDS);
+        private String uniqueTag;
+        private volatile boolean built;
+
+        public Builder entity(EntityLocal val) {
+            this.entity = checkNotNull(val, "entity");
+            return this;
+        }
+        public Builder addSensor(WindowsPerformanceCounterPollConfig<?> config) {
+            polls.add(config);
+            return this;
+        }
+        public Builder addSensor(String performanceCounterName, AttributeSensor<?> sensor) {
+            return addSensor(new WindowsPerformanceCounterPollConfig(sensor).performanceCounterName(checkNotNull(performanceCounterName, "performanceCounterName")));
+        }
+        public Builder addSensors(Map<String, AttributeSensor> sensors) {
+            for (Map.Entry<String, AttributeSensor> entry : sensors.entrySet()) {
+                addSensor(entry.getKey(), entry.getValue());
+            }
+            return this;
+        }
+        public Builder period(Duration period) {
+            this.period = checkNotNull(period, "period");
+            return this;
+        }
+        public Builder period(long millis) {
+            return period(millis, TimeUnit.MILLISECONDS);
+        }
+        public Builder period(long val, TimeUnit units) {
+            return period(Duration.of(val, units));
+        }
+        public Builder uniqueTag(String uniqueTag) {
+            this.uniqueTag = uniqueTag;
+            return this;
+        }
+        public WindowsPerformanceCounterFeed build() {
+            built = true;
+            WindowsPerformanceCounterFeed result = new WindowsPerformanceCounterFeed(this);
+            result.setEntity(checkNotNull(entity, "entity"));
+            result.start();
+            return result;
+        }
+        @Override
+        protected void finalize() {
+            if (!built) log.warn("WindowsPerformanceCounterFeed.Builder created, but build() never called");
+        }
+    }
+
+    /**
+     * For rebind; do not call directly; use builder
+     */
+    public WindowsPerformanceCounterFeed() {
+    }
+
+    protected WindowsPerformanceCounterFeed(Builder builder) {
+        List<WindowsPerformanceCounterPollConfig<?>> polls = Lists.newArrayList();
+        for (WindowsPerformanceCounterPollConfig<?> config : builder.polls) {
+            if (!config.isEnabled()) continue;
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            WindowsPerformanceCounterPollConfig<?> configCopy = new WindowsPerformanceCounterPollConfig(config);
+            if (configCopy.getPeriod() < 0) configCopy.period(builder.period);
+            polls.add(configCopy);
+        }
+        config().set(POLLS, polls);
+        initUniqueTag(builder.uniqueTag, polls);
+    }
+
+    @Override
+    protected void preStart() {
+        Collection<WindowsPerformanceCounterPollConfig<?>> polls = getConfig(POLLS);
+        
+        long minPeriod = Integer.MAX_VALUE;
+        List<String> performanceCounterNames = Lists.newArrayList();
+        for (WindowsPerformanceCounterPollConfig<?> config : polls) {
+            minPeriod = Math.min(minPeriod, config.getPeriod());
+            performanceCounterNames.add(config.getPerformanceCounterName());
+        }
+        
+        Iterable<String> allParams = ImmutableList.<String>builder()
+                .add("(Get-Counter")
+                .add("-Counter")
+                .add(JOINER_ON_COMMA.join(Iterables.transform(performanceCounterNames, QuoteStringFunction.INSTANCE)))
+                .add("-SampleInterval")
+                .add("2") // TODO: extract SampleInterval as a config key
+                .add(").CounterSamples")
+                .add("|")
+                .add("Format-Table")
+                .add(String.format("@{Expression={$_.Path};width=%d},@{Expression={$_.CookedValue};width=%<d}", OUTPUT_COLUMN_WIDTH))
+                .add("-HideTableHeaders")
+                .add("|")
+                .add("Out-String")
+                .add("-Width")
+                .add(String.valueOf(OUTPUT_COLUMN_WIDTH * 2))
+                .build();
+        String command = JOINER_ON_SPACE.join(allParams);
+        log.debug("Windows performance counter poll command for {} will be: {}", entity, command);
+
+        GetPerformanceCountersJob<WinRmToolResponse> job = new GetPerformanceCountersJob(getEntity(), command);
+        getPoller().scheduleAtFixedRate(
+                new CallInEntityExecutionContext(entity, job),
+                new SendPerfCountersToSensors(getEntity(), polls),
+                minPeriod);
+    }
+
+    private static class GetPerformanceCountersJob<T> implements Callable<T> {
+
+        private final Entity entity;
+        private final String command;
+
+        GetPerformanceCountersJob(Entity entity, String command) {
+            this.entity = entity;
+            this.command = command;
+        }
+
+        @Override
+        public T call() throws Exception {
+            WinRmMachineLocation machine = EffectorTasks.getWinRmMachine(entity);
+            WinRmToolResponse response = machine.executePsScript(command);
+            return (T)response;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Poller<WinRmToolResponse> getPoller() {
+        return (Poller<WinRmToolResponse>) super.getPoller();
+    }
+
+    /**
+     * A {@link java.util.concurrent.Callable} that wraps another {@link java.util.concurrent.Callable}, where the
+     * inner {@link java.util.concurrent.Callable} is executed in the context of a
+     * specific entity.
+     *
+     * @param <T> The type of the {@link java.util.concurrent.Callable}.
+     */
+    private static class CallInEntityExecutionContext<T> implements Callable<T> {
+        private final Callable<T> job;
+        private EntityLocal entity;
+
+        private CallInEntityExecutionContext(EntityLocal entity, Callable<T> job) {
+            this.job = job;
+            this.entity = entity;
+        }
+
+        @Override
+        public T call() throws Exception {
+            ExecutionContext executionContext = ((EntityInternal) entity).getManagementSupport().getExecutionContext();
+            return executionContext.submit(Maps.newHashMap(), job).get();
+        }
+    }
+
+    @VisibleForTesting
+    static class SendPerfCountersToSensors implements PollHandler<WinRmToolResponse> {
+        private final EntityLocal entity;
+        private final List<WindowsPerformanceCounterPollConfig<?>> polls;
+        private final Set<AttributeSensor<?>> failedAttributes = Sets.newLinkedHashSet();
+        private static final Pattern MACHINE_NAME_LOOKBACK_PATTERN = Pattern.compile(String.format("(?<=\\\\\\\\.{0,%d})\\\\.*", OUTPUT_COLUMN_WIDTH));
+        
+        public SendPerfCountersToSensors(EntityLocal entity, Collection<WindowsPerformanceCounterPollConfig<?>> polls) {
+            this.entity = entity;
+            this.polls = ImmutableList.copyOf(polls);
+        }
+
+        @Override
+        public boolean checkSuccess(WinRmToolResponse val) {
+            // TODO not just using statusCode; also looking at absence of stderr.
+            // Status code is (empirically) unreliable: it returns 0 sometimes even when failed 
+            // (but never returns non-zero on success).
+            if (val.getStatusCode() != 0) return false;
+            String stderr = val.getStdErr();
+            if (stderr == null || stderr.length() != 0) return false;
+            String out = val.getStdOut();
+            if (out == null || out.length() == 0) return false;
+            return true;
+        }
+
+        @Override
+        public void onSuccess(WinRmToolResponse val) {
+            for (String pollResponse : val.getStdOut().split("\r\n")) {
+                if (Strings.isNullOrEmpty(pollResponse)) {
+                    continue;
+                }
+                String path = pollResponse.substring(0, OUTPUT_COLUMN_WIDTH - 1);
+                // The performance counter output prepends the sensor name with "\\<machinename>" so we need to remove it
+                Matcher machineNameLookbackMatcher = MACHINE_NAME_LOOKBACK_PATTERN.matcher(path);
+                if (!machineNameLookbackMatcher.find()) {
+                    continue;
+                }
+                String name = machineNameLookbackMatcher.group(0).trim();
+                String rawValue = pollResponse.substring(OUTPUT_COLUMN_WIDTH).replaceAll("^\\s+", "");
+                WindowsPerformanceCounterPollConfig<?> config = getPollConfig(name);
+                Class<?> clazz = config.getSensor().getType();
+                AttributeSensor<Object> attribute = (AttributeSensor<Object>) Sensors.newSensor(clazz, config.getSensor().getName(), config.getDescription());
+                try {
+                    Object value = TypeCoercions.coerce(rawValue, TypeToken.of(clazz));
+                    entity.sensors().set(attribute, value);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (failedAttributes.add(attribute)) {
+                        log.warn("Failed to coerce value '{}' to {} for {} -> {}", new Object[] {rawValue, clazz, entity, attribute});
+                    } else {
+                        if (log.isTraceEnabled()) log.trace("Failed (repeatedly) to coerce value '{}' to {} for {} -> {}", new Object[] {rawValue, clazz, entity, attribute});
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onFailure(WinRmToolResponse val) {
+            log.error("Windows Performance Counter query did not respond as expected. exitcode={} stdout={} stderr={}",
+                    new Object[]{val.getStatusCode(), val.getStdOut(), val.getStdErr()});
+            for (WindowsPerformanceCounterPollConfig<?> config : polls) {
+                Class<?> clazz = config.getSensor().getType();
+                AttributeSensor<?> attribute = Sensors.newSensor(clazz, config.getSensor().getName(), config.getDescription());
+                entity.sensors().set(attribute, null);
+            }
+        }
+
+        @Override
+        public void onException(Exception exception) {
+            log.error("Detected exception while retrieving Windows Performance Counters from entity " +
+                    entity.getDisplayName(), exception);
+            for (WindowsPerformanceCounterPollConfig<?> config : polls) {
+                entity.sensors().set(Sensors.newSensor(config.getSensor().getClass(), config.getPerformanceCounterName(), config.getDescription()), null);
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "" + polls;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString()+"["+getDescription()+"]";
+        }
+
+        private WindowsPerformanceCounterPollConfig<?> getPollConfig(String sensorName) {
+            for (WindowsPerformanceCounterPollConfig<?> poll : polls) {
+                if (poll.getPerformanceCounterName().equalsIgnoreCase(sensorName)) {
+                    return poll;
+                }
+            }
+            throw new IllegalStateException(String.format("%s not found in configured polls: %s", sensorName, polls));
+        }
+    }
+
+    static class PerfCounterValueIterator implements Iterator<String> {
+
+        // This pattern matches the contents of the first field, and optionally matches the rest of the line as
+        // further fields. Feed the second match back into the pattern again to get the next field, and repeat until
+        // all fields are discovered.
+        protected static final Pattern splitPerfData = Pattern.compile("^\"([^\\\"]*)\"((,\"[^\\\"]*\")*)$");
+
+        private Matcher matcher;
+
+        public PerfCounterValueIterator(String input) {
+            matcher = splitPerfData.matcher(input);
+            // Throw away the first element (the timestamp) (and also confirm that we have a pattern match)
+            checkArgument(hasNext(), "input "+input+" does not match expected pattern "+splitPerfData.pattern());
+            next();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return matcher != null && matcher.find();
+        }
+
+        @Override
+        public String next() {
+            String next = matcher.group(1);
+
+            String remainder = matcher.group(2);
+            if (!Strings.isNullOrEmpty(remainder)) {
+                assert remainder.startsWith(",");
+                remainder = remainder.substring(1);
+                matcher = splitPerfData.matcher(remainder);
+            } else {
+                matcher = null;
+            }
+
+            return next;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static enum QuoteStringFunction implements Function<String, String> {
+        INSTANCE;
+
+        @Nullable
+        @Override
+        public String apply(@Nullable String input) {
+            return input != null ? "\"" + input + "\"" : null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
new file mode 100644
index 0000000..5d3a0c8
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
@@ -0,0 +1,80 @@
+/*
+ * 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.location.winrm;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * When attached to an entity, this will monitor for when an {@link WinRmMachineLocation} is added to that entity
+ * (e.g. when a VM has been provisioned for it).
+ * 
+ * The policy will then add a sensor that advertises the Administrator login details.
+ * 
+ * A preferred mechanism would be for an external key-management tool to provide access to the credentials.
+ */
+@Beta
+public class AdvertiseWinrmLoginPolicy extends AbstractPolicy implements SensorEventListener<Location> {
+
+    // TODO Would like support user-creation over WinRM
+    
+    private static final Logger LOG = LoggerFactory.getLogger(AdvertiseWinrmLoginPolicy.class);
+
+    public static final AttributeSensor<String> VM_USER_CREDENTIALS = Sensors.newStringSensor(
+            "vm.user.credentials",
+            "The \"<user> : <password> @ <hostname>:<port>\"");
+
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, this);
+    }
+
+    @Override
+    public void onEvent(SensorEvent<Location> event) {
+        final Entity entity = event.getSource();
+        final Location loc = event.getValue();
+        if (loc instanceof WinRmMachineLocation) {
+            advertiseUserAsync(entity, (WinRmMachineLocation)loc);
+        }
+    }
+
+    protected void advertiseUserAsync(final Entity entity, final WinRmMachineLocation machine) {
+        String user = machine.getUser();
+        String hostname = machine.getHostname();
+        int port = machine.config().get(WinRmMachineLocation.WINRM_PORT);
+        String password = machine.config().get(WinRmMachineLocation.PASSWORD);
+        
+        String creds = user + " : " + password + " @ " +hostname + ":" + port;
+        
+        LOG.info("Advertising user "+user+" @ "+hostname+":"+port);
+        
+        ((EntityLocal)entity).sensors().set(VM_USER_CREDENTIALS, creds);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
new file mode 100644
index 0000000..3a7dbd5
--- /dev/null
+++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java
@@ -0,0 +1,362 @@
+/*
+ * 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.location.winrm;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.location.MachineDetails;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.access.PortForwardManager;
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.net.HostAndPort;
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import io.cloudsoft.winrm4j.winrm.WinRmTool;
+import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
+
+public class WinRmMachineLocation extends AbstractLocation implements MachineLocation {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WinRmMachineLocation.class);
+
+    // FIXME Respect `port` config when using {@link WinRmTool}
+    public static final ConfigKey<Integer> WINRM_PORT = ConfigKeys.newIntegerConfigKey(
+            "port",
+            "WinRM port to use when connecting to the remote machine",
+            5985);
+    
+    // TODO merge with {link SshTool#PROP_USER} and {@link SshMachineLocation#user}
+    public static final ConfigKey<String> USER = ConfigKeys.newStringConfigKey("user",
+            "Username to use when connecting to the remote machine");
+
+    // TODO merge with {link SshTool#PROP_PASSWORD}
+    public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password",
+            "Password to use when connecting to the remote machine");
+
+    public static final ConfigKey<Integer> COPY_FILE_CHUNK_SIZE_BYTES = ConfigKeys.newIntegerConfigKey("windows.copy.file.size.bytes",
+            "Size of file chunks (in bytes) to be used when copying a file to the remote server", 1024);
+
+     public static final ConfigKey<InetAddress> ADDRESS = ConfigKeys.newConfigKey(
+            InetAddress.class,
+            "address",
+            "Address of the remote machine");
+
+    public static final ConfigKey<Integer> EXECUTION_ATTEMPTS = ConfigKeys.newIntegerConfigKey(
+            "windows.exec.attempts",
+            "Number of attempts to execute a remote command",
+            1);
+    
+    // TODO See SshTool#PROP_SSH_TRIES, where it was called "sshTries"; remove duplication? Merge into one well-named thing?
+    public static final ConfigKey<Integer> EXEC_TRIES = ConfigKeys.newIntegerConfigKey(
+            "execTries", 
+            "Max number of times to attempt WinRM operations", 
+            10);
+
+    public static final ConfigKey<Iterable<String>> PRIVATE_ADDRESSES = ConfigKeys.newConfigKey(
+            new TypeToken<Iterable<String>>() {},
+            "privateAddresses",
+            "Private addresses of this machine, e.g. those within the private network", 
+            null);
+
+    public static final ConfigKey<Map<Integer, String>> TCP_PORT_MAPPINGS = ConfigKeys.newConfigKey(
+            new TypeToken<Map<Integer, String>>() {},
+            "tcpPortMappings",
+            "NAT'ed ports, giving the mapping from private TCP port to a public host:port", 
+            null);
+
+    @Override
+    public InetAddress getAddress() {
+        return getConfig(ADDRESS);
+    }
+
+    @Override
+    public OsDetails getOsDetails() {
+        return null;
+    }
+
+    @Override
+    public MachineDetails getMachineDetails() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getHostname() {
+        InetAddress address = getAddress();
+        return (address != null) ? address.getHostAddress() : null;
+    }
+
+    @Nullable
+    protected String getHostAndPort() {
+        String host = getHostname();
+        return (host == null) ? null : host + ":" + config().get(WINRM_PORT);
+    }
+
+    @Override
+    public Set<String> getPublicAddresses() {
+        InetAddress address = getAddress();
+        return (address == null) ? ImmutableSet.<String>of() : ImmutableSet.of(address.getHostAddress());
+    }
+    
+    @Override
+    public Set<String> getPrivateAddresses() {
+        Iterable<String> result = getConfig(PRIVATE_ADDRESSES);
+        return (result == null) ? ImmutableSet.<String>of() : ImmutableSet.copyOf(result);
+    }
+
+    public WinRmToolResponse executeScript(String script) {
+        return executeScript(ImmutableList.of(script));
+    }
+
+    public WinRmToolResponse executeScript(List<String> script) {
+        int execTries = getRequiredConfig(EXEC_TRIES);
+        Collection<Throwable> exceptions = Lists.newArrayList();
+        for (int i = 0; i < execTries; i++) {
+            try {
+                return executeScriptNoRetry(script);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (i == (execTries+1)) {
+                    LOG.info("Propagating WinRM exception (attempt "+(i+1)+" of "+execTries+")", e);
+                } else if (i == 0) {
+                    LOG.warn("Ignoring WinRM exception and retrying (attempt "+(i+1)+" of "+execTries+")", e);
+                } else {
+                    LOG.debug("Ignoring WinRM exception and retrying (attempt "+(i+1)+" of "+execTries+")", e);
+                }
+                exceptions.add(e);
+            }
+        }
+        throw Exceptions.propagate("failed to execute shell script", exceptions);
+    }
+
+    protected WinRmToolResponse executeScriptNoRetry(List<String> script) {
+        WinRmTool winRmTool = WinRmTool.connect(getHostAndPort(), getUser(), getPassword());
+        WinRmToolResponse response = winRmTool.executeScript(script);
+        return response;
+    }
+
+    public WinRmToolResponse executePsScript(String psScript) {
+        return executePsScript(ImmutableList.of(psScript));
+    }
+
+    public WinRmToolResponse executePsScript(List<String> psScript) {
+        int execTries = getRequiredConfig(EXEC_TRIES);
+        Collection<Throwable> exceptions = Lists.newArrayList();
+        for (int i = 0; i < execTries; i++) {
+            try {
+                return executePsScriptNoRetry(psScript);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (i == (execTries+1)) {
+                    LOG.info("Propagating WinRM exception (attempt "+(i+1)+" of "+execTries+")", e);
+                } else if (i == 0) {
+                    LOG.warn("Ignoring WinRM exception and retrying after 5 seconds (attempt "+(i+1)+" of "+execTries+")", e);
+                    Time.sleep(Duration.FIVE_SECONDS);
+                } else {
+                    LOG.debug("Ignoring WinRM exception and retrying after 5 seconds (attempt "+(i+1)+" of "+execTries+")", e);
+                    Time.sleep(Duration.FIVE_SECONDS);
+                }
+                exceptions.add(e);
+            }
+        }
+        throw Exceptions.propagate("failed to execute powershell script", exceptions);
+    }
+
+    public WinRmToolResponse executePsScriptNoRetry(List<String> psScript) {
+        WinRmTool winRmTool = WinRmTool.connect(getHostAndPort(), getUser(), getPassword());
+        WinRmToolResponse response = winRmTool.executePs(psScript);
+        return response;
+    }
+
+    public int copyTo(File source, String destination) {
+        FileInputStream sourceStream = null;
+        try {
+            sourceStream = new FileInputStream(source);
+            return copyTo(sourceStream, destination);
+        } catch (FileNotFoundException e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            if (sourceStream != null) {
+                Streams.closeQuietly(sourceStream);
+            }
+        }
+    }
+
+    public int copyTo(InputStream source, String destination) {
+        executePsScript(ImmutableList.of("rm -ErrorAction SilentlyContinue " + destination));
+        try {
+            int chunkSize = getConfig(COPY_FILE_CHUNK_SIZE_BYTES);
+            byte[] inputData = new byte[chunkSize];
+            int bytesRead;
+            int expectedFileSize = 0;
+            while ((bytesRead = source.read(inputData)) > 0) {
+                byte[] chunk;
+                if (bytesRead == chunkSize) {
+                    chunk = inputData;
+                } else {
+                    chunk = Arrays.copyOf(inputData, bytesRead);
+                }
+                executePsScript(ImmutableList.of("If ((!(Test-Path " + destination + ")) -or ((Get-Item '" + destination + "').length -eq " +
+                        expectedFileSize + ")) {Add-Content -Encoding Byte -path " + destination +
+                        " -value ([System.Convert]::FromBase64String(\"" + new String(Base64.encodeBase64(chunk)) + "\"))}"));
+                expectedFileSize += bytesRead;
+            }
+
+            return 0;
+        } catch (java.io.IOException e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+
+        // Register any pre-existing port-mappings with the PortForwardManager
+        Map<Integer, String> tcpPortMappings = getConfig(TCP_PORT_MAPPINGS);
+        if (tcpPortMappings != null) {
+            PortForwardManager pfm = (PortForwardManager) getManagementContext().getLocationRegistry().resolve("portForwardManager(scope=global)");
+            for (Map.Entry<Integer, String> entry : tcpPortMappings.entrySet()) {
+                int targetPort = entry.getKey();
+                HostAndPort publicEndpoint = HostAndPort.fromString(entry.getValue());
+                if (!publicEndpoint.hasPort()) {
+                    throw new IllegalArgumentException("Invalid portMapping ('"+entry.getValue()+"') for port "+targetPort+" in machine "+this);
+                }
+                pfm.associate(publicEndpoint.getHostText(), publicEndpoint, this, targetPort);
+            }
+        }
+    }
+    public String getUser() {
+        return config().get(USER);
+    }
+
+    private String getPassword() {
+        return config().get(PASSWORD);
+    }
+
+    private <T> T getRequiredConfig(ConfigKey<T> key) {
+        return checkNotNull(getConfig(key), "key %s must be set", key);
+    }
+    
+    public static String getDefaultUserMetadataString() {
+        // Using an encoded command obviates the need to escape
+        String unencodePowershell = Joiner.on("\r\n").join(ImmutableList.of(
+                // Allow TS connections
+                "$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -ComputerName $env:computername -Namespace root\\CIMV2\\TerminalServices -Authentication PacketPrivacy",
+                "$RDP.SetAllowTSConnections(1,1)",
+                "Set-ExecutionPolicy Unrestricted -Force",
+                // Set unlimited values for remote execution limits
+                "Set-Item WSMan:\\localhost\\Shell\\MaxConcurrentUsers 100",
+                "Set-Item WSMan:\\localhost\\Shell\\MaxMemoryPerShellMB 0",
+                "Set-Item WSMan:\\localhost\\Shell\\MaxProcessesPerShell 0",
+                "Set-Item WSMan:\\localhost\\Shell\\MaxShellsPerUser 0",
+                "New-ItemProperty \"HKLM:\\System\\CurrentControlSet\\Control\\LSA\" -Name \"SuppressExtendedProtection\" -Value 1 -PropertyType \"DWord\"",
+                // The following allows scripts to re-authenticate with local credential - this is required
+                // as certain operations cannot be performed with remote credentials
+                "$allowed = @('WSMAN/*')",
+                "$key = 'hklm:\\SOFTWARE\\Policies\\Microsoft\\Windows\\CredentialsDelegation'",
+                "if (!(Test-Path $key)) {",
+                "    md $key",
+                "}",
+                "New-ItemProperty -Path $key -Name AllowFreshCredentials -Value 1 -PropertyType Dword -Force",
+                "New-ItemProperty -Path $key -Name AllowFreshCredentialsWhenNTLMOnly -Value 1 -PropertyType Dword -Force",
+                "$credKey = Join-Path $key 'AllowFreshCredentials'",
+                "if (!(Test-Path $credKey)) {",
+                "    md $credkey",
+                "}",
+                "$ntlmKey = Join-Path $key 'AllowFreshCredentialsWhenNTLMOnly'",
+                "if (!(Test-Path $ntlmKey)) {",
+                "    md $ntlmKey",
+                "}",
+                "$i = 1",
+                "$allowed |% {",
+                "    # Script does not take into account existing entries in this key",
+                "    New-ItemProperty -Path $credKey -Name $i -Value $_ -PropertyType String -Force",
+                "    New-ItemProperty -Path $ntlmKey -Name $i -Value $_ -PropertyType String -Force",
+                "    $i++",
+                "}"
+        ));
+
+        String encoded = new String(Base64.encodeBase64(unencodePowershell.getBytes(Charsets.UTF_16LE)));
+        return "winrm quickconfig -q & " +
+                "winrm set winrm/config/service/auth @{Basic=\"true\"} & " +
+                "winrm set winrm/config/service/auth @{CredSSP=\"true\"} & " +
+                "winrm set winrm/config/client/auth @{CredSSP=\"true\"} & " +
+                "winrm set winrm/config/client @{AllowUnencrypted=\"true\"} & " +
+                "winrm set winrm/config/service @{AllowUnencrypted=\"true\"} & " +
+                "winrm set winrm/config/winrs @{MaxConcurrentUsers=\"100\"} & " +
+                "winrm set winrm/config/winrs @{MaxMemoryPerShellMB=\"0\"} & " +
+                "winrm set winrm/config/winrs @{MaxProcessesPerShell=\"0\"} & " +
+                "winrm set winrm/config/winrs @{MaxShellsPerUser=\"0\"} & " +
+                "netsh advfirewall firewall add rule name=RDP dir=in protocol=tcp localport=3389 action=allow profile=any & " +
+                "netsh advfirewall firewall add rule name=WinRM dir=in protocol=tcp localport=5985 action=allow profile=any & " +
+                "powershell -EncodedCommand " + encoded;
+        /* TODO: Find out why scripts with new line characters aren't working on AWS. The following appears as if it *should*
+           work but doesn't - the script simply isn't run. By connecting to the machine via RDP, you can get the script
+           from 'http://169.254.169.254/latest/user-data', and running it at the command prompt works, but for some
+           reason the script isn't run when the VM is provisioned
+        */
+//        return Joiner.on("\r\n").join(ImmutableList.of(
+//                "winrm quickconfig -q",
+//                "winrm set winrm/config/service/auth @{Basic=\"true\"}",
+//                "winrm set winrm/config/client @{AllowUnencrypted=\"true\"}",
+//                "winrm set winrm/config/service @{AllowUnencrypted=\"true\"}",
+//                "netsh advfirewall firewall add rule name=RDP dir=in protocol=tcp localport=3389 action=allow profile=any",
+//                "netsh advfirewall firewall add rule name=WinRM dir=in protocol=tcp localport=5985 action=allow profile=any",
+//                // Using an encoded command necessitates the need to escape. The unencoded command is as follows:
+//                // $RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -ComputerName $env:computername -Namespace root\CIMV2\TerminalServices -Authentication PacketPrivacy
+//                // $Result = $RDP.SetAllowTSConnections(1,1)
+//                "powershell -EncodedCommand JABSAEQAUAAgAD0AIABHAGUAdAAtAFcAbQBpAE8AYgBqAGUAYwB0ACAALQBDAGwAYQBzAHMAI" +
+//                        "ABXAGkAbgAzADIAXwBUAGUAcgBtAGkAbgBhAGwAUwBlAHIAdgBpAGMAZQBTAGUAdAB0AGkAbgBnACAALQBDAG8AbQBwA" +
+//                        "HUAdABlAHIATgBhAG0AZQAgACQAZQBuAHYAOgBjAG8AbQBwAHUAdABlAHIAbgBhAG0AZQAgAC0ATgBhAG0AZQBzAHAAY" +
+//                        "QBjAGUAIAByAG8AbwB0AFwAQwBJAE0AVgAyAFwAVABlAHIAbQBpAG4AYQBsAFMAZQByAHYAaQBjAGUAcwAgAC0AQQB1A" +
+//                        "HQAaABlAG4AdABpAGMAYQB0AGkAbwBuACAAUABhAGMAawBlAHQAUAByAGkAdgBhAGMAeQANAAoAJABSAGUAcwB1AGwAd" +
+//                        "AAgAD0AIAAkAFIARABQAC4AUwBlAHQAQQBsAGwAbwB3AFQAUwBDAG8AbgBuAGUAYwB0AGkAbwBuAHMAKAAxACwAMQApAA=="
+//        ));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedLiveTest.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedLiveTest.java b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedLiveTest.java
new file mode 100644
index 0000000..de7d481
--- /dev/null
+++ b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedLiveTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.feed.windows;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * WindowsPerformanceCounterFeed Live Test.
+ * <p>
+ * This test is currently disabled. To run, you must configure a location named {@code WindowsLiveTest}
+ * or adapt the {@link #LOCATION_SPEC} below.
+ * <p>
+ * The location must provide Windows nodes that are running an SSH server on port 22. The login credentials must
+ * be either be auto-detectable or configured in brooklyn.properties in the usual fashion.
+ * <p>
+ * Here is an example configuration from brooklyn.properties for a pre-configured Windows VM
+ * running an SSH server with public key authentication:
+ * <pre>
+ * {@code brooklyn.location.named.WindowsLiveTest=byon:(hosts="ec2-xx-xxx-xxx-xx.eu-west-1.compute.amazonaws.com")
+ * brooklyn.location.named.WindowsLiveTest.user=Administrator
+ * brooklyn.location.named.WindowsLiveTest.privateKeyFile = ~/.ssh/id_rsa
+ * brooklyn.location.named.WindowsLiveTest.publicKeyFile = ~/.ssh/id_rsa.pub
+ * }</pre>
+ * The location must by {@code byon} or another primitive type. Unfortunately, it's not possible to
+ * use a jclouds location, as adding a dependency on brooklyn-locations-jclouds would cause a
+ * cyclic dependency.
+ */
+public class WindowsPerformanceCounterFeedLiveTest extends BrooklynAppLiveTestSupport {
+
+    final static AttributeSensor<Double> CPU_IDLE_TIME = Sensors.newDoubleSensor("cpu.idleTime", "");
+    final static AttributeSensor<Integer> TELEPHONE_LINES = Sensors.newIntegerSensor("telephone.lines", "");
+
+    private static final String LOCATION_SPEC = "named:WindowsLiveTest";
+
+    private Location loc;
+    private EntityLocal entity;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        
+        Map<String,?> allFlags = MutableMap.<String,Object>builder()
+                .put("tags", ImmutableList.of(getClass().getName()))
+                .build();
+        MachineProvisioningLocation<? extends MachineLocation> provisioningLocation =
+                (MachineProvisioningLocation<? extends MachineLocation>)
+                        mgmt.getLocationRegistry().resolve(LOCATION_SPEC, allFlags);
+        loc = provisioningLocation.obtain(ImmutableMap.of());
+
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test(groups={"Live","Disabled"}, enabled=false)
+    public void testRetrievesPerformanceCounters() throws Exception {
+        // We can be pretty sure that a Windows instance in the cloud will have zero telephone lines...
+        entity.sensors().set(TELEPHONE_LINES, 42);
+        WindowsPerformanceCounterFeed feed = WindowsPerformanceCounterFeed.builder()
+                .entity(entity)
+                .addSensor("\\Processor(_total)\\% Idle Time", CPU_IDLE_TIME)
+                .addSensor("\\Telephony\\Lines", TELEPHONE_LINES)
+                .build();
+        try {
+            EntityTestUtils.assertAttributeEqualsEventually(entity, TELEPHONE_LINES, 0);
+        } finally {
+            feed.stop();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java
new file mode 100644
index 0000000..059797b
--- /dev/null
+++ b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.feed.windows;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+
+import io.cloudsoft.winrm4j.winrm.WinRmToolResponse;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class WindowsPerformanceCounterFeedTest extends BrooklynAppUnitTestSupport {
+
+    private Location loc;
+    private EntityLocal entity;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = new LocalhostMachineProvisioningLocation();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        app.start(ImmutableList.of(loc));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private static final Logger log = LoggerFactory.getLogger(WindowsPerformanceCounterFeedTest.class);
+
+    @Test
+    public void testIteratorWithSingleValue() {
+        Iterator<?> iterator = new WindowsPerformanceCounterFeed
+                .PerfCounterValueIterator("\"10/14/2013 15:28:24.406\",\"0.000000\"");
+        assertTrue(iterator.hasNext());
+        assertEquals(iterator.next(), "0.000000");
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void testIteratorWithMultipleValues() {
+        Iterator<?> iterator = new WindowsPerformanceCounterFeed
+                .PerfCounterValueIterator("\"10/14/2013 15:35:50.582\",\"8803.000000\",\"405622.000000\"");
+        assertTrue(iterator.hasNext());
+        assertEquals(iterator.next(), "8803.000000");
+        assertTrue(iterator.hasNext());
+        assertEquals(iterator.next(), "405622.000000");
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void testSendPerfCountersToSensors() {
+        AttributeSensor<String> stringSensor = Sensors.newStringSensor("foo.bar");
+        AttributeSensor<Integer> integerSensor = Sensors.newIntegerSensor("bar.baz");
+        AttributeSensor<Double> doubleSensor = Sensors.newDoubleSensor("baz.quux");
+
+        Collection<WindowsPerformanceCounterPollConfig<?>> polls = ImmutableSet.<WindowsPerformanceCounterPollConfig<?>>of(
+                new WindowsPerformanceCounterPollConfig(stringSensor).performanceCounterName("\\processor information(_total)\\% processor time"),
+                new WindowsPerformanceCounterPollConfig(integerSensor).performanceCounterName("\\integer.sensor"),
+                new WindowsPerformanceCounterPollConfig(doubleSensor).performanceCounterName("\\double\\sensor\\with\\multiple\\sub\\paths")
+        );
+
+        WindowsPerformanceCounterFeed.SendPerfCountersToSensors sendPerfCountersToSensors = new WindowsPerformanceCounterFeed.SendPerfCountersToSensors(entity, polls);
+
+        assertNull(entity.getAttribute(stringSensor));
+
+        StringBuilder responseBuilder = new StringBuilder();
+        // NOTE: This builds the response in a different order to which they are passed to the SendPerfCountersToSensors constructor
+        // this tests that the values are applied correctly even if the (possibly non-deterministic) order in which
+        // they are returned by the Get-Counter scriptlet is different
+        addMockResponse(responseBuilder, "\\\\machine.name\\double\\sensor\\with\\multiple\\sub\\paths", "3.1415926");
+        addMockResponse(responseBuilder, "\\\\win-lge7uj2blau\\processor information(_total)\\% processor time", "99.9");
+        addMockResponse(responseBuilder, "\\\\machine.name\\integer.sensor", "15");
+
+        sendPerfCountersToSensors.onSuccess(new WinRmToolResponse(responseBuilder.toString(), "", 0));
+
+        EntityTestUtils.assertAttributeEquals(entity, stringSensor, "99.9");
+        EntityTestUtils.assertAttributeEquals(entity, integerSensor, 15);
+        EntityTestUtils.assertAttributeEquals(entity, doubleSensor, 3.1415926);
+    }
+
+    private void addMockResponse(StringBuilder responseBuilder, String path, String value) {
+        responseBuilder.append(path);
+        responseBuilder.append(Strings.repeat(" ", 200 - (path.length() + value.length())));
+        responseBuilder.append(value);
+        responseBuilder.append("\r\n");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicyTest.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicyTest.java b/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicyTest.java
new file mode 100644
index 0000000..53b95c8
--- /dev/null
+++ b/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicyTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.location.winrm;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class AdvertiseWinrmLoginPolicyTest extends BrooklynAppUnitTestSupport {
+
+    @Test
+    public void testAdvertisesMachineLoginDetails() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .policy(PolicySpec.create(AdvertiseWinrmLoginPolicy.class)));
+        
+        WinRmMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(WinRmMachineLocation.class)
+                .configure("address", "1.2.3.4")
+                .configure("user", "myuser")
+                .configure("port", 5678)
+                .configure("password", "mypassword"));
+        app.start(ImmutableList.of(machine));
+        
+        String expected = "myuser : mypassword @ 1.2.3.4:5678";
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, AdvertiseWinrmLoginPolicy.VM_USER_CREDENTIALS, expected);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8fa4df7d/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/WinRmMachineLocationTest.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/WinRmMachineLocationTest.java b/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/WinRmMachineLocationTest.java
new file mode 100644
index 0000000..9fa0fe7
--- /dev/null
+++ b/software/winrm/src/test/java/org/apache/brooklyn/location/winrm/WinRmMachineLocationTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.location.winrm;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.util.net.Networking;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class WinRmMachineLocationTest extends BrooklynAppUnitTestSupport {
+
+    @Test
+    public void testConfigurePrivateAddresses() throws Exception {
+        WinRmMachineLocation host = mgmt.getLocationManager().createLocation(LocationSpec.create(WinRmMachineLocation.class)
+                .configure("address", Networking.getLocalHost())
+                .configure(WinRmMachineLocation.PRIVATE_ADDRESSES, ImmutableList.of("1.2.3.4"))
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true));
+
+        assertEquals(host.getPrivateAddresses(), ImmutableSet.of("1.2.3.4"));
+    }
+}


Mime
View raw message