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