You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@brooklyn.apache.org by sjcorbett <gi...@git.apache.org> on 2017/01/19 17:30:59 UTC

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

GitHub user sjcorbett opened a pull request:

    https://github.com/apache/brooklyn-server/pull/529

    LocationNetworkInfoCustomizer

    `LocationNetworkInfoCustomizer` is a special customiser used by `JcloudsLocation` to determine the address and credentials with which a location should be contacted. By default it should behave as the previous logic in `JcloudsLocation`, but it exposes options to have Brooklyn prefer to contact VMs on private addresses and can be injected on a per-entity basis. For example:
    
    ```yaml
    services:
    - type: server
      location: the-same-private-network-as-brooklyn
      brooklyn.initializers:
      - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
        brooklyn.config:
          mode: ONLY_PRIVATE
    - type: server
      location: another-cloud
      # implicit use of the default network info customizer.
    ```
    
    Would result in the first entity being managed on the instance's private address (and deployment failing if this was not possible) and the second being managed on its public address. Graceful fallback is possible by replacing `ONLY_PRIVATE` with `PREFER_PRIVATE`. There are `PUBLIC` variants of each of these.
    
    These changes were prompted by a thread on the Brooklyn mailing list last December: http://markmail.org/thread/xpr6nsyimv7goewy. Some of the items discussed in that thread have been left for future work. It depends on PRs #497 and #524.
    
    I'd like opinions on:
    * naming: "BasicLocationNetworkInfoCustomizer" is cumbersome.
    * whether the class should extend `BasicJcloudsLocationCustomizer`. I can see it being useful to anyone that wants to subclass BLNIC but at the moment it's totally unused.
    * whether we should include the `publishNetworks` option right now. It controls the publication of `host.address.{public,private}.n` sensors. The idea is good but the feature feels a bit half-baked right now.
    


You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/sjcorbett/brooklyn-server feature/location-network-info

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/brooklyn-server/pull/529.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #529
    
----

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98000373
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -2610,24 +2514,32 @@ public boolean apply(@Nullable WinRmMachineLocation machine) {
             return credsSuccessful.get();
         }
     
    -    protected LoginCredentials waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    -        LoginCredentials nodeCreds = node.getCredentials();
    +    protected LoginCredentials waitForSshableGuessCredentials(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        // See https://issues.apache.org/jira/browse/BROOKLYN-186
    +        // Handle where jclouds gives us the wrong login user (!) and both a password + ssh key.
    +        // Try all the permutations to find the one that works.
    +        Iterable<LoginCredentials> credentialsToTry = generateCredentials(node.getCredentials(), setup.get(LOGIN_USER));
    +        return waitForSshable(computeService, node, managementHostAndPort, credentialsToTry, setup);
    +    }
    +
    +    /** @deprecated Since 0.11.0. Use {@link #waitForSshableGuessCredentials} instead. */
    +    @Deprecated
    +    protected LoginCredentials waitForSshable(ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        return waitForSshableGuessCredentials(computeService, node, managementHostAndPort, setup);
    +    }
    +
    +    /** @return An Iterable of credentials based on nodeCreds containing different parameters. */
    +    Iterable<LoginCredentials> generateCredentials(LoginCredentials nodeCreds, @Nullable String loginUserOverride) {
             String nodeUser = nodeCreds.getUser();
    -        String loginUserOverride = setup.get(LOGIN_USER);
             Set<String> users = MutableSet.of();
    -
             if (Strings.isNonBlank(nodeUser)) {
                 users.add(nodeUser);
             }
    -
             if (Strings.isNonBlank(loginUserOverride)) {
                 users.add(loginUserOverride);
             }
    -
    -        // See https://issues.apache.org/jira/browse/BROOKLYN-186
    -        // Handle where jclouds gives us the wrong login user (!) and both a password + ssh key.
    -        // Try all the permutations to find the one that works.
    -        List<LoginCredentials> credentialsToTry = Lists.newArrayList();
    +        List<LoginCredentials> credentialsToTry = new ArrayList<>();
    +        Lists.newArrayList();
    --- End diff --
    
    Remove.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97502612
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldTestCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (testCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    --- End diff --
    
    p.s. I don't mean this PR _requires_ this change as such; but it might be worth adding a "TODO" to highlight the point.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97999588
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    --- End diff --
    
    The check is still vulnerable to the local network IP poisoning. The reachability check will succeed immediately for the local IP, not giving enough time for the public IP to become available. And then the credential check will fail.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97984500
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,32 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    +        boolean waitForWinRmable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
    +        boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
    +        boolean skipJcloudsSshing = Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
    +        boolean windows = isWindows(node, setup);
    +        boolean waitForConnectable = windows ? waitForWinRmable : waitForSshable;
    +        String pollForFirstReachable = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS);
    --- End diff --
    
    With the reachability check doing credentials check I think `POLL_FOR_FIRST_REACHABLE_ADDRESS` and `WAIT_FOR_XXX_AVAILABLE` pretty much overlap now. Suggest (at the very least) adding a TODO somewhere to deprecate one of them.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97982714
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    --- End diff --
    
    Suggest skipping if `NetworkMode` is set. If it's not set then it acts similarly to auto `NetworkMode`. When outside of Amazon it will resolve to the public and when inside will resolve to the private IP. Could cause problems when Brooklyn & managed machines are inside Amazon but on different (non-reachable) subnets. Wonder how DNS resolving behaves then.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97510372
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -2610,24 +2525,32 @@ public boolean apply(@Nullable WinRmMachineLocation machine) {
             return credsSuccessful.get();
         }
     
    -    protected LoginCredentials waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    -        LoginCredentials nodeCreds = node.getCredentials();
    +    protected LoginCredentials waitForSshableGuessCredentials(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    --- End diff --
    
    This new method isn't being called, but the newly deprecated method at line 2564 still is, at JcloudsLocation.java:811.  I guess you meant here to replace the calls to `waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort hostAndPort, Iterable<LoginCredentials> credentialsToTry, ConfigBag setup)` with calls to `waitForSshableGuessCredentials`.    But shouldn't `waitForSshableGuessCredentials` be calling `waitForSshable( HostAndPort hostAndPort, Iterable<LoginCredentials> credentialsToTry, ConfigBag setup)`  at line 2533 rather than the deprecated five param version?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97974618
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,32 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    +        boolean waitForWinRmable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
    +        boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
    +        boolean skipJcloudsSshing = Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
    +        boolean windows = isWindows(node, setup);
    +        boolean waitForConnectable = windows ? waitForWinRmable : waitForSshable;
    +        String pollForFirstReachable = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS);
    +        boolean pollEnabled = !"false".equalsIgnoreCase(pollForFirstReachable);
    +        Duration pollTimeout = "true".equals(pollForFirstReachable) ? Duration.FIVE_MINUTES : Duration.of(pollForFirstReachable);
    --- End diff --
    
    `Duration.of(pollForFirstReachable)` will throw if it's `false`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98003723
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/LocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,46 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.jclouds.compute.domain.NodeMetadata;
    +
    +import com.google.common.annotations.Beta;
    +
    +/**
    + * LocationNetworkInfoCustomizers are used by {@link JcloudsLocation} to determine the host and port
    + * on which connections to locations should be made and the credentials that should be used.
    + *
    + * @see JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER
    + */
    +@Beta
    +public interface LocationNetworkInfoCustomizer extends JcloudsLocationCustomizer {
    --- End diff --
    
    Don't think this should be a `JcloudsLocationCustomizer`. Yes it customises the behaviour of the location. My thinking is that `JcloudsLocationCustomizer` is not properly named though - it's too generic and anything altering the behaviour of the location matches it. Something better describing it would be `LocationLifecycleCallback`.
    To back the above - you are not implementing any of the methods of the customiser.
    
    The primary task of this interface is to return the addresses we can ssh into. A name more fitting in this description would be `LocationConnectivityResolver`, `LocationAddressResolver`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server issue #529: ConnectivityResolver

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on the issue:

    https://github.com/apache/brooklyn-server/pull/529
  
    looks good, will merge



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97370289
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -1738,97 +1740,6 @@ protected LoginCredentials initTemplateForCreateUser(Template template, ConfigBa
             return userCreation.credentials();
         }
     
    -    private static class ResolveOptions {
    -        boolean pollForFirstReachableAddress;
    -        boolean expectConnectable;
    -        boolean isWindows;
    -        boolean propagatePollForReachableFailure;
    -
    -        ResolveOptions pollForFirstReachableAddress(boolean val) {
    -            pollForFirstReachableAddress = val;
    -            return this;
    -        }
    -        ResolveOptions expectConnectable(boolean val) {
    -            expectConnectable = val;
    -            return this;
    -        }
    -        ResolveOptions windows(boolean val) {
    -            isWindows = val;
    -            return this;
    -        }
    -        public ResolveOptions propagatePollForReachableFailure(boolean val) {
    -            this.propagatePollForReachableFailure = val;
    -            return this;
    -        }
    -    }
    -
    -    /**
    -     * Infers the hostAndPort to use for subsequent creation of the
    -     * {@link JcloudsSshMachineLocation} or {@link JcloudsWinRmMachineLocation}.
    -     * This is expected to be the login host:port, for connecting to the VM
    -     * via ssh or WinRM.
    -     *
    -     * However, some VMs provisioned will not be sshable or reachable at all.
    -     * In such cases, this method will likely return the first address returned by
    -     * jclouds.
    -     *
    -     * For AWS, if we are allowed to SSH to the VM to find out its DNS name, then we'll
    -     * return that fully qualified name (which we expect to be reachable from inside
    -     * and outside the AWS region).
    -     */
    -    private HostAndPort resolveManagementHostAndPort(
    -            NodeMetadata node, Optional<LoginCredentials> userCredentials,
    -            Optional<HostAndPort> hostAndPortOverride, ConfigBag config, ResolveOptions options) {
    -        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(LOOKUP_AWS_HOSTNAME));
    -        String provider = config.get(CLOUD_PROVIDER);
    -        if (provider == null) provider= getProvider();
    -        int defaultPort;
    -        if (options.isWindows) {
    -            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    -        } else {
    -            defaultPort = node.getLoginPort();
    -        }
    -
    -        if (hostAndPortOverride.isPresent()) {
    -            // Don't try to resolve it; just use it
    -            int port = hostAndPortOverride.get().hasPort() ? hostAndPortOverride.get().getPort() : defaultPort;
    -            return HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    -        }
    -        if (options.expectConnectable && userCredentials.isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    -            // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    -            // (normally?!) a good way to refer to the VM from both inside and outside of the
    -            // region.
    -            Maybe<String> result = getHostnameAws(node, hostAndPortOverride, Suppliers.ofInstance(userCredentials.get()), config);
    -            if (result.isPresent()) {
    -                return HostAndPort.fromParts(result.get(), defaultPort);
    -            }
    -        }
    -        if (options.expectConnectable && options.pollForFirstReachableAddress) {
    -            try {
    -                String firstReachableAddress = getFirstReachableAddress(node, config);
    -                return HostAndPort.fromParts(firstReachableAddress, defaultPort);
    -            } catch (RuntimeException e) {
    -                if (options.propagatePollForReachableFailure) {
    -                    throw Exceptions.propagate(e);
    -                } else {
    -                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    -                            getCreationString(config), node);
    -                }
    -            }
    -        }
    -
    -        Iterable<String> addresses = Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses());
    -        for (String address : addresses) {
    -            if (isAddressResolvable(address)) {
    -                return HostAndPort.fromParts(address, defaultPort);
    -            }
    -        }
    -        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
    -                new Object[]{addresses, getCreationString(config), node});
    -        String host = Iterables.get(addresses, 0);
    -        return HostAndPort.fromParts(host, defaultPort);
    -    }
    -
         private boolean isAddressResolvable(String addr) {
    --- End diff --
    
    This can be deleted now.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97828758
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    --- End diff --
    
    So it logins to the machine to find which IP to login to :).
    Note that this is different from the next case. It similarly fetches the first reachable IP, but then connects to it and executes a `curl` command, returning the `hostname` value.
    Would make more sense to move this after the code which already figured out the correct IPs and use them to fetch the hostname. I'd expect this to be available from EC2 API so in future could improve it to fetch it directly through jclouds.
    For amazon it would return the `hostname`. Wonder how it interacts with `PREFER_PRIVATE`. Seems the return value gets coerced to `InetAddress` (which will resolve the hostname from the POV of the Brooklyn instance) and set on the `MachineLocation`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97325707
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    --- End diff --
    
    maybe `validate_credentials`  - `test_credentials` sounds like it means "credentials for a test"


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97512096
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldTestCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (testCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                return getReachableAddresses(node, config, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        }
    +
    +        Iterable<String> addresses = getNodeAddressesWithMode(node);
    +        LOG.debug("Using first resolvable address in {} for management candidates of {}", Iterables.toString(addresses), location);
    +        for (String address : addresses) {
    +            if (isAddressResolvable(address)) {
    +                return ImmutableList.of(HostAndPort.fromParts(address, defaultPort));
    +            }
    +        }
    +
    +        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
    +                new Object[]{addresses, location.getCreationString(config), node});
    +        String host = Iterables.getFirst(addresses, null);
    +        if (host != null) {
    +            return ImmutableList.of(HostAndPort.fromParts(host, defaultPort));
    +        } else {
    +            return ImmutableList.of();
    +        }
    +    }
    +
    +    /**
    +     * Returns all reachable addresses according to {@link #getReachableAddressesPredicate}.
    +     * Iterators are ordered according to the configured {@link #getMode() mode}.
    +     */
    +    protected Iterable<HostAndPort> getReachableAddresses(NodeMetadata node, ConfigBag setup, Duration timeout) {
    +        if (timeout == null) timeout = Duration.FIVE_MINUTES;
    +        Iterable<String> candidates = getNodeAddressesWithMode(node);
    +        Predicate<? super HostAndPort> pollForFirstReachableHostAndPortPredicate = getReachableAddressesPredicate(setup);
    +        return JcloudsUtil.getReachableAddresses(candidates, node.getLoginPort(), timeout, pollForFirstReachableHostAndPortPredicate);
    +    }
    +
    +    protected Iterable<String> getNodeAddressesWithMode(NodeMetadata node) {
    +        switch (getMode()) {
    +        case ONLY_PRIVATE:
    +            return node.getPrivateAddresses();
    +        case ONLY_PUBLIC:
    +            return node.getPublicAddresses();
    +        case PREFER_PRIVATE:
    +            return Iterables.concat(node.getPrivateAddresses(), node.getPublicAddresses());
    +        case PREFER_PUBLIC:
    +        default:
    +            return Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses());
    +        }
    +    }
    +
    +    protected boolean isAddressResolvable(String addr) {
    +        try {
    +            Networking.getInetAddressWithFixedName(addr);
    +            return true; // fine, it resolves
    +        } catch (RuntimeException e) {
    +            Exceptions.propagateIfFatal(e);
    +            return false;
    +        }
    +    }
    +
    +    protected Predicate<? super HostAndPort> getReachableAddressesPredicate(ConfigBag setup) {
    +        if (setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE) != null) {
    +            LOG.debug("{} polling for first reachable address with {}",
    +                    this, setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE));
    +            return setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE);
    +        } else {
    +            LOG.debug("{} polling for first reachable address with instance of {}",
    +                    this, JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE.getName());
    +
    +            Class<? extends Predicate<? super HostAndPort>> predicateType =
    +                    setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE);
    +
    +            Map<String, Object> args = MutableMap.of();
    +            ConfigUtils.addUnprefixedConfigKeyInConfigBack(
    +                    JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".", setup, args);
    +            try {
    +                return predicateType.getConstructor(Map.class).newInstance(args);
    +            } catch (NoSuchMethodException | IllegalAccessException e) {
    +                try {
    +                    return predicateType.newInstance();
    +                } catch (IllegalAccessException | InstantiationException newInstanceException) {
    +                    throw Exceptions.propagate("Instantiating " + predicateType + " failed.", newInstanceException);
    +                }
    +            } catch (InvocationTargetException | InstantiationException e) {
    +                throw Exceptions.propagate("Problem trying to instantiate " + predicateType + " with Map constructor.", e);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    protected boolean shouldTestCredentials() {
    +        return Boolean.TRUE.equals(config().get(TEST_CREDENTIALS));
    +    }
    +
    +    protected boolean testCredential(
    +            JcloudsLocation location, HostAndPort hostAndPort, LoginCredentials credentials,
    +            ConfigBag config, boolean isWindows) {
    +        try {
    +            if (isWindows) {
    +                location.waitForWinRmAvailable(credentials, hostAndPort, config);
    +            } else {
    +                location.waitForSshable(hostAndPort, ImmutableList.of(credentials), config);
    +            }
    +            return true;
    +        } catch (IllegalStateException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected Iterable<LoginCredentials> getCredentialCandidates(
    +            JcloudsLocation location, NodeMetadata node, ManagementAddressResolveOptions options, ConfigBag setup) {
    +        LoginCredentials userCredentials = null;
    +        // Figure out which login credentials to use. We only make a connection with
    +        // initialCredentials when jclouds didn't do any sshing and wait for connectable is true.
    +        // 0. if jclouds didn't do anything and we should wait for the machine then initial credentials is
    +        //    whatever waitForSshable determines and then create the user ourselves.
    +        if (options.skipJcloudsSshing() && options.expectReachable()) {
    +            if (options.isWindows()) {
    +                return ImmutableList.of(options.getInitialCredentials());
    +            } else {
    +                return location.generateCredentials(node.getCredentials(), setup.get(JcloudsLocationConfig.LOGIN_USER));
    +            }
    +        }
    +
    +        // 1. Were they configured by the user?
    +        LoginCredentials customCredentials = setup.get(JcloudsLocation.CUSTOM_CREDENTIALS);
    --- End diff --
    
    Is it worth some validation here for non-blank?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server issue #529: ConnectivityResolver

Posted by sjcorbett <gi...@git.apache.org>.
Github user sjcorbett commented on the issue:

    https://github.com/apache/brooklyn-server/pull/529
  
    @geomacy @neykov thanks for your thorough comments. Please see 728fb46 for changes addressing your suggestions and c9fcf15 for a simplification of `ReachableSocketFinder`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server issue #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on the issue:

    https://github.com/apache/brooklyn-server/pull/529
  
    * naming
      * I think a more appropriate name for the interface would be `ConnectivityResolver` and for the implementation `DefaultConnectivityResolver` (could even drop the location part).
    * whether the class should extend BasicJcloudsLocationCustomizer:
      * No
    * whether we should include the publishNetworks option right now:
      * it's a nice to have feature, though not sure the current format is optimal, see comments above. If in doubt could include it disabled by default.
    * whether ManagementAddressResolveOptions is the right way to convey information between the location and the customiser: 
      * My first thought was that `JcloudsLocation`  + `config` should be enough, potentially including `credentialsOverride`. Why include both `config` and `options` if it can be reconstructed on the other side.  I favour minimal interfaces which don't duplicate data. But on the other hand I'm happy to leave as is, especially if the bean is reused at other places. Could clean it up in future.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97966691
  
    --- Diff: core/src/main/java/org/apache/brooklyn/util/core/task/BasicTask.java ---
    @@ -377,7 +377,9 @@ public T getUnchecked() {
         public synchronized void blockUntilStarted() {
             blockUntilStarted(null);
         }
    -    
    +
    +    // TODO: This should log a message if timeout is null and the method blocks for an unreasonably long time -
    +    // it probably means someone called .get() and forgot to submit the task.
    --- End diff --
    
    Should differentiate between not queued to a parent task (because the user forgot the submit it) and not yet submitted to an executor because a previous task failed which is a valid case.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97353258
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,30 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    --- End diff --
    
    Could push the knowledge about the configuration keys into the constructor of the class itself, and just make this
    ```java
        protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
                NodeMetadata x, ConfigBag setup, Optional<HostAndPort> x, Optional<LoginCredentials> x) {
            return new ManagementAddressResolveOptions(setup)
                    .hostAndPortOverride(sshHostAndPortOverride)
                    .initialCredentials(node.getCredentials())
                    .userCredentials(userCredentials));
        }
    ```


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98007567
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -752,58 +783,32 @@ protected MachineLocation obtainOnce(ConfigBag setup) throws NoMachinesAvailable
                     sshHostAndPortOverride = Optional.absent();
                 }
     
    -            LoginCredentials initialCredentials = node.getCredentials();
    +            ManagementAddressResolveOptions resolveOptions = getManagementAddressResolveOptions(
    +                    node, setup, sshHostAndPortOverride, Optional.fromNullable(userCredentials));
    +            LocationNetworkInfoCustomizer networkInfoCustomizer = getLocationNetworkInfoCustomizer(setup);
     
    -            final HostAndPort managementHostAndPort = resolveManagementHostAndPort(
    -                    node, Optional.fromNullable(userCredentials), sshHostAndPortOverride, setup,
    -                    new ResolveOptions()
    -                            .windows(windows)
    -                            .expectConnectable(waitForConnectable)
    -                            .pollForFirstReachableAddress(!"false".equalsIgnoreCase(setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS)))
    -                            .propagatePollForReachableFailure(true));
    +            ManagementAddressResolveResult hapandc = networkInfoCustomizer.resolve(this, node, setup, resolveOptions);
    +            final HostAndPort managementHostAndPort = hapandc.hostAndPort();
    +            LoginCredentials creds = hapandc.credentials();
    +            LOG.info("Using host-and-port={} and user={} when connecting to {}",
    +                    new Object[]{managementHostAndPort, creds.getUser(), node});
     
    -            if (skipJcloudsSshing) {
    -                if (waitForConnectable) {
    -                    if (windows) {
    -                        // TODO Does jclouds support any windows user setup?
    -                        initialCredentials = waitForWinRmAvailable(initialCredentials, managementHostAndPort, setup);
    -                    } else {
    -                        initialCredentials = waitForSshable(computeService, node, managementHostAndPort, setup);
    -                    }
    -                    userCredentials = createUser(computeService, node, managementHostAndPort, initialCredentials, setup);
    +            if (skipJcloudsSshing && waitForConnectable) {
    +                LoginCredentials createdCredentials = createUser(computeService, node, managementHostAndPort, creds, setup);
    +                if (createdCredentials != null) {
    +                    userCredentials = createdCredentials;
                     }
                 }
    -
    -            // Figure out which login-credentials to use
    -            LoginCredentials customCredentials = setup.get(CUSTOM_CREDENTIALS);
    -            if (customCredentials != null) {
    -                userCredentials = customCredentials;
    -                //set userName and other data, from these credentials
    -                Object oldUsername = setup.put(USER, customCredentials.getUser());
    -                LOG.debug("node {} username {} / {} (customCredentials)", new Object[] { node, customCredentials.getUser(), oldUsername });
    -                if (customCredentials.getOptionalPassword().isPresent()) setup.put(PASSWORD, customCredentials.getOptionalPassword().get());
    -                if (customCredentials.getOptionalPrivateKey().isPresent()) setup.put(PRIVATE_KEY_DATA, customCredentials.getOptionalPrivateKey().get());
    -            }
    -            if (userCredentials == null || (!userCredentials.getOptionalPassword().isPresent() && !userCredentials.getOptionalPrivateKey().isPresent())) {
    -                // We either don't have any userCredentials, or it is missing both a password/key.
    -                // TODO See waitForSshable, which now handles if the node.getLoginCredentials has both a password+key
    -                userCredentials = extractVmCredentials(setup, node, initialCredentials);
    -            }
                 if (userCredentials == null) {
    -                // TODO See waitForSshable, which now handles if the node.getLoginCredentials has both a password+key
    -                userCredentials = extractVmCredentials(setup, node, initialCredentials);
    -            }
    -            if (userCredentials != null) {
    -                node = NodeMetadataBuilder.fromNodeMetadata(node).credentials(userCredentials).build();
    -            } else {
    -                // only happens if something broke above...
    -                userCredentials = LoginCredentials.fromCredentials(node.getCredentials());
    +                userCredentials = creds;
                 }
    +
                 // store the credentials, in case they have changed
                 putIfPresentButDifferent(setup, JcloudsLocationConfig.PASSWORD, userCredentials.getOptionalPassword().orNull());
                 putIfPresentButDifferent(setup, JcloudsLocationConfig.PRIVATE_KEY_DATA, userCredentials.getOptionalPrivateKey().orNull());
     
                 // Wait for the VM to be reachable over SSH
    +            // TODO: this has already been tested by locationnetworkinfocustomizer
    --- End diff --
    
    Guard it with with a `!CHECK_CREDENTIALS` then?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97974169
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,32 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    +        boolean waitForWinRmable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
    +        boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
    +        boolean skipJcloudsSshing = Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
    +        boolean windows = isWindows(node, setup);
    +        boolean waitForConnectable = windows ? waitForWinRmable : waitForSshable;
    +        String pollForFirstReachable = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS);
    --- End diff --
    
    Description for `POLL_FOR_FIRST_REACHABLE_ADDRESS` needs updating in light of the `NetworkMode` changes.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97369331
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldTestCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (testCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    --- End diff --
    
    It would be nice to factor out the AWS specifics into a separate tool; I know this has come from the existing JcloudsLocation code but as this is a new class maybe there is an opportunity to avoid repeating this pattern?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by sjcorbett <gi...@git.apache.org>.
Github user sjcorbett commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r101047658
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,30 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    --- End diff --
    
    Think I misunderstood you here. Were you referring to the constructor for `ManagementAddressResolveOptions`?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server issue #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on the issue:

    https://github.com/apache/brooklyn-server/pull/529
  
    I tested this successfully:  put a brooklyn on ibm-bluebox-lon-pub, and used it to deploy the blueprint 
    ```
    location: ibm-bluebox-lon-pub
    services:
    - type: server
      name: custom
      brooklyn.initializers:
      - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
        brooklyn.config:
          mode: ONLY_PRIVATE
    - type: server
      name: default
      # implicit use of the default network info customizer.
    ```
    
    i.e. both servers are deployed to the same location as brooklyn, which is configured to create a public address (`jclouds.openstack-nova.auto-create-floating-ips: true`).
    
    The server 'custom' above was assigned a host address on the private network, while the 'default' one was assigned it on the public network:
    ```
    $ for ename in custom default ; do printf "%s " ${ename} ;  br app app1 ent ${ename} sensor host.address ; done
    custom 10.101.1.232
    default 169.50.81.104
    ```


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97972489
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    --- End diff --
    
    Do you have a particular usage of the sensors in mind? I'm wondering whether an array in a single sensor will be more useful. It can be processed by transformers and such. Having separate keys with unknown count makes any address > `host.address.xxx.0` useless.
    
    Is it possible that we have more than one hostname per machine? That's re the `todo hostnames` comment.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98002399
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    --- End diff --
    
    Would happen only when jclouds returns no IP addresses at all so could amend with "jclouds did not return any IP addresses matching " + getMode() 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by sjcorbett <gi...@git.apache.org>.
Github user sjcorbett commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97610554
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -2607,24 +2522,32 @@ public boolean apply(@Nullable WinRmMachineLocation machine) {
             return credsSuccessful.get();
         }
     
    -    protected LoginCredentials waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    -        LoginCredentials nodeCreds = node.getCredentials();
    +    protected LoginCredentials waitForSshableGuessCredentials(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        // See https://issues.apache.org/jira/browse/BROOKLYN-186
    +        // Handle where jclouds gives us the wrong login user (!) and both a password + ssh key.
    +        // Try all the permutations to find the one that works.
    +        Iterable<LoginCredentials> credentialsToTry = generateCredentials(node.getCredentials(), setup.get(LOGIN_USER));
    +        return waitForSshable(computeService, node, managementHostAndPort, credentialsToTry, setup);
    +    }
    +
    +    /** @deprecated Since 0.11.0. Use {@link #waitForSshableGuessCredentials} instead. */
    +    @Deprecated
    +    protected LoginCredentials waitForSshable(ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        return waitForSshable(computeService, node, managementHostAndPort, setup);
    --- End diff --
    
    Good spot.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97503683
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldTestCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (testCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                return getReachableAddresses(node, config, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        }
    +
    +        Iterable<String> addresses = getNodeAddressesWithMode(node);
    +        LOG.debug("Using first resolvable address in {} for management candidates of {}", Iterables.toString(addresses), location);
    +        for (String address : addresses) {
    +            if (isAddressResolvable(address)) {
    +                return ImmutableList.of(HostAndPort.fromParts(address, defaultPort));
    +            }
    +        }
    +
    +        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
    +                new Object[]{addresses, location.getCreationString(config), node});
    +        String host = Iterables.getFirst(addresses, null);
    +        if (host != null) {
    +            return ImmutableList.of(HostAndPort.fromParts(host, defaultPort));
    +        } else {
    +            return ImmutableList.of();
    +        }
    +    }
    +
    +    /**
    +     * Returns all reachable addresses according to {@link #getReachableAddressesPredicate}.
    +     * Iterators are ordered according to the configured {@link #getMode() mode}.
    +     */
    +    protected Iterable<HostAndPort> getReachableAddresses(NodeMetadata node, ConfigBag setup, Duration timeout) {
    +        if (timeout == null) timeout = Duration.FIVE_MINUTES;
    +        Iterable<String> candidates = getNodeAddressesWithMode(node);
    +        Predicate<? super HostAndPort> pollForFirstReachableHostAndPortPredicate = getReachableAddressesPredicate(setup);
    +        return JcloudsUtil.getReachableAddresses(candidates, node.getLoginPort(), timeout, pollForFirstReachableHostAndPortPredicate);
    +    }
    +
    +    protected Iterable<String> getNodeAddressesWithMode(NodeMetadata node) {
    +        switch (getMode()) {
    +        case ONLY_PRIVATE:
    +            return node.getPrivateAddresses();
    +        case ONLY_PUBLIC:
    +            return node.getPublicAddresses();
    +        case PREFER_PRIVATE:
    +            return Iterables.concat(node.getPrivateAddresses(), node.getPublicAddresses());
    +        case PREFER_PUBLIC:
    +        default:
    +            return Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses());
    +        }
    +    }
    +
    +    protected boolean isAddressResolvable(String addr) {
    +        try {
    +            Networking.getInetAddressWithFixedName(addr);
    +            return true; // fine, it resolves
    +        } catch (RuntimeException e) {
    +            Exceptions.propagateIfFatal(e);
    +            return false;
    +        }
    +    }
    +
    +    protected Predicate<? super HostAndPort> getReachableAddressesPredicate(ConfigBag setup) {
    --- End diff --
    
    This could maybe be folded into the options class; similar thought to note [here](https://github.com/apache/brooklyn-server/pull/529#discussion_r97357864)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97975373
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -1917,15 +1820,18 @@ public JcloudsMachineLocation registerMachine(ConfigBag setup) throws NoMachines
         protected JcloudsMachineLocation registerMachineLocation(ConfigBag setup, NodeMetadata node) {
             ComputeService computeService = getComputeService(setup);
             boolean windows = isWindows(node, setup);
    -        
    -        HostAndPort managementHostAndPort = resolveManagementHostAndPort(
    -                node, Optional.<LoginCredentials>absent(), Optional.<HostAndPort>absent(), setup,
    -                new ResolveOptions()
    -                        .windows(windows)
    -                        .expectConnectable(true)
    -                        .propagatePollForReachableFailure(false)
    -                        .pollForFirstReachableAddress(!"false".equalsIgnoreCase(setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS))));
    -        
    +
    +        // Not publishing networks since they should have previously been published.
    +        ManagementAddressResolveOptions options = getManagementAddressResolveOptions(
    +                node, setup, Optional.<HostAndPort>absent(), Optional.fromNullable(node.getCredentials()))
    +                .publishNetworks(false);
    --- End diff --
    
    This could be called when adopting and managing a machine created outside of Brooklyn, correct? If so the entity we are attaching it to won't have the networks published yet.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by sjcorbett <gi...@git.apache.org>.
Github user sjcorbett commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r101334570
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    --- End diff --
    
    Isn't this an intentional trade-off per the discussion about grace periods in `ReachableSocketFinder` in https://github.com/apache/brooklyn-server/pull/497?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98004942
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -429,6 +429,11 @@ protected CloudMachineNamer getCloudMachineNamer(ConfigBag config) {
             return result;
         }
     
    +    public LocationNetworkInfoCustomizer getLocationNetworkInfoCustomizer(ConfigBag setup) {
    +        LocationNetworkInfoCustomizer configured = setup.get(LOCATION_NETWORK_INFO_CUSTOMIZER);
    --- End diff --
    
    I think a config key is suitable for this. Just FYI there is the [`extension`](https://github.com/apache/brooklyn-server/blob/master/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocation.java#L729-L749) API as well. We could improve it in future and use it to customise the behaviour of the location with pluggable components, leaving the keys for (declarative) configuration.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97972121
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    --- End diff --
    
    Not sure what this comment means?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97984984
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/ManagementAddressResolveOptions.java ---
    @@ -0,0 +1,158 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.domain.LoginCredentials;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * Holds parameters to be used by a {@link LocationNetworkInfoCustomizer}.
    + */
    +@Beta
    +public class ManagementAddressResolveOptions {
    +
    +    private Duration pollTimeout;
    +    private boolean waitForSshable;
    +    private boolean pollForFirstReachableAddress;
    +    private boolean expectReachable;
    +    private boolean isWindows;
    +    private boolean propagatePollForReachableFailure;
    +    private LoginCredentials initialCredentials;
    +    private Optional<LoginCredentials> userCredentials = Optional.absent();
    +    private Optional<HostAndPort> hostAndPortOverride = Optional.absent();
    +    private boolean skipJcloudsSshing;
    +    private boolean publishNetworkSensors = true;
    +    private Predicate<? super HostAndPort> reachableAddressPredicate;
    +
    +    public ManagementAddressResolveOptions expectReachable(boolean expectConnectable) {
    +        this.expectReachable = expectConnectable;
    +        return this;
    +    }
    +
    +    /** Indicate the host and port that should be used over all others. Normally used in tandem with a port forwarder. */
    +    public ManagementAddressResolveOptions hostAndPortOverride(Optional<HostAndPort> hostAndPortOverride) {
    +        this.hostAndPortOverride = hostAndPortOverride;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions initialCredentials(LoginCredentials initialCredentials) {
    +        this.initialCredentials = initialCredentials;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions pollForFirstReachableAddress(boolean pollForFirstReachableAddress) {
    +        this.pollForFirstReachableAddress = pollForFirstReachableAddress;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions propagatePollForReachableFailure(boolean propagatePollForReachableFailure) {
    +        this.propagatePollForReachableFailure = propagatePollForReachableFailure;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions pollTimeout(Duration pollTimeout) {
    +        this.pollTimeout = pollTimeout;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions skipJcloudsSshing(boolean skipJcloudsSshing) {
    +        this.skipJcloudsSshing = skipJcloudsSshing;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions waitForSshable(boolean waitForSshable) {
    +        this.waitForSshable = waitForSshable;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions isWindows(boolean windows) {
    +        isWindows = windows;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions userCredentials(Optional<LoginCredentials> userCredentials) {
    +        this.userCredentials = userCredentials;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions publishNetworks(boolean publishNetworks) {
    +        this.publishNetworkSensors = publishNetworks;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions reachableAddressPredicate(Predicate<? super HostAndPort> predicate) {
    +        this.reachableAddressPredicate = predicate;
    +        return this;
    +    }
    +
    +    public Duration getPollTimeout() {
    +        return pollTimeout;
    +    }
    +
    +    public boolean waitForSshable() {
    +        return waitForSshable;
    +    }
    +
    +    public boolean pollForFirstReachableAddress() {
    +        return pollForFirstReachableAddress;
    +    }
    +
    +    public boolean expectReachable() {
    +        return expectReachable;
    +    }
    +
    +    public boolean isWindows() {
    +        return isWindows;
    +    }
    +
    +    public boolean propagatePollForReachableFailure() {
    +        return propagatePollForReachableFailure;
    +    }
    +
    +    public LoginCredentials getInitialCredentials() {
    +        return initialCredentials;
    +    }
    +
    +    public Optional<LoginCredentials> getUserCredentials() {
    +        return userCredentials;
    +    }
    +
    +    public Optional<HostAndPort> getHostAndPortOverride() {
    +        return hostAndPortOverride;
    +    }
    +
    +    public boolean skipJcloudsSshing() {
    +        return skipJcloudsSshing;
    +    }
    +
    +    public boolean publishNetworkSensors() {
    +        return publishNetworkSensors;
    +    }
    +
    +    public Predicate<? super HostAndPort> getReachableAddressPredicate() {
    --- End diff --
    
    Remove `get` prefix to follow convention.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: ConnectivityResolver

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r102683289
  
    --- Diff: utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java ---
    @@ -116,9 +114,9 @@ public HostAndPort findOpenSocketOnNode(final Iterable<? extends HostAndPort> so
         }
     
         /**
    -     * @return A lazily computed Iterable containing present values for the elements of sockets that are
    -     * reachable according to {@link #socketTester} and absent values for those not. Checks are concurrent
    -     * and the elements in the Iterable are ordered according to their position in sockets.
    +     * @return An Iterable containing present values for the elements of sockets that are reachable
    +     * according to {@link #socketTester} and absent values for those not. Checks are concurrent.
    +     * The iterable returned is ordered according sockets.
    --- End diff --
    
    typo "according to"


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97816042
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    --- End diff --
    
    If it's port forwarding then presumably it'd be the external interface. What if the user has specified `PREFER_PRIVATE`?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98002022
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    --- End diff --
    
    A more appropriate message would be `No credentials configured for location`. Since `credentialCandidates` is empty.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: ConnectivityResolver

Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:

    https://github.com/apache/brooklyn-server/pull/529


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97353855
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    --- End diff --
    
    Keep consistency of spelling, `customizer`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97352385
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/ManagementAddressResolveOptions.java ---
    @@ -0,0 +1,147 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.domain.LoginCredentials;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Optional;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * Holds parameters to be used by a {@link LocationNetworkInfoCustomizer}.
    + */
    +@Beta
    +public class ManagementAddressResolveOptions {
    --- End diff --
    
    +1, I think this is much preferable to managing these settings as separate local variables as previously.
    
    See comment on where it is used.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98008318
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -2476,37 +2382,10 @@ protected String getFirstReachableAddress(NodeMetadata node, ConfigBag setup) {
             String result;
    --- End diff --
    
    Don't think the method `getFirstReachableAddress` is still referenced. Mark it deprecated.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97983346
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,32 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    +        boolean waitForWinRmable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
    +        boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
    +        boolean skipJcloudsSshing = Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
    +        boolean windows = isWindows(node, setup);
    +        boolean waitForConnectable = windows ? waitForWinRmable : waitForSshable;
    +        String pollForFirstReachable = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS);
    +        boolean pollEnabled = !"false".equalsIgnoreCase(pollForFirstReachable);
    +        Duration pollTimeout = "true".equals(pollForFirstReachable) ? Duration.FIVE_MINUTES : Duration.of(pollForFirstReachable);
    +        Predicate<? super HostAndPort> reachablePredicate = getReachableAddressesPredicate(setup);
    +        return new ManagementAddressResolveOptions()
    +                .expectReachable(waitForConnectable)
    +                .hostAndPortOverride(sshHostAndPortOverride)
    +                .initialCredentials(node.getCredentials())
    +                .isWindows(windows)
    +                .pollForFirstReachableAddress(pollEnabled)
    +                .pollTimeout(pollTimeout)
    +                .propagatePollForReachableFailure(true)
    +                .reachableAddressPredicate(reachablePredicate)
    +                .skipJcloudsSshing(skipJcloudsSshing)
    +                .userCredentials(userCredentials)
    +                .waitForSshable(waitForSshable);
    --- End diff --
    
    Can use `expectReachable` instead. But suggest renaming it to `waitForConnectable` to keep the convention.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server issue #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on the issue:

    https://github.com/apache/brooklyn-server/pull/529
  
    Will have a look through this now, however, on the points for opinion above, my 2� worth is:
    
    - naming: "Customizer" isn't really needed (we don't insist on e.g. VanillaSoftwareProcessEntity), and this class isn't really "Basic" (which to me refers to the "default no-op" behaviour of `BasicJcloudsLocationCustomizer`). Also "Location" doesn't add much to the description, when you already have "Network".  The role of the class is about choosing which network you want, not really about "Info" as such so how about `org.apache.brooklyn.location.jclouds.NetworkPreference`.
    - what to extend: I suppose another way to look at it is, "if someone changes behaviour in `BasicJcloudsLocationCustomizer` is that something we would want this to inherit"?   No strong feeling but I tend to think that we would want that, so it should extend it.
    - `publishNetworks` - I'd say prefer leaving things as separate as possible, so would suggest leaving that to a separate PR.
    - resolve options - will have a look.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97985603
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && options.getReachableAddressPredicate() != null) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                final Predicate<? super HostAndPort> predicate = options.getReachableAddressPredicate();
    +                return getReachableAddresses(node, predicate, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        } else if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.warn("{} was configured to expect node {} to be reachable and to poll for its first reachable " +
    --- End diff --
    
    Throw instead. Prev implementation would throw with [NPE](https://github.com/apache/brooklyn-server/blob/master/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java#L67).
    The `POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE` key has a default so users should take special care to get to here :). Can use `WAIT_FOR_XXX` or `POLL_FOR_FIRST_REACHABLE` to skip above checks instead.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97523950
  
    --- Diff: locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizerTest.java ---
    @@ -0,0 +1,261 @@
    +/*
    + * 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.jclouds;
    +
    +import static org.testng.Assert.assertEquals;
    +import static org.testng.Assert.assertFalse;
    +import static org.testng.Assert.assertTrue;
    +import static org.testng.Assert.fail;
    --- End diff --
    
    unused


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r98001108
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && options.getReachableAddressPredicate() != null) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                final Predicate<? super HostAndPort> predicate = options.getReachableAddressPredicate();
    +                return getReachableAddresses(node, predicate, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        } else if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.warn("{} was configured to expect node {} to be reachable and to poll for its first reachable " +
    +                    "address but the predicate to determine reachability was null", this, node);
    +        }
    +
    +        Iterable<String> addresses = getNodeAddressesWithMode(node);
    +        LOG.debug("Using first resolvable address in {} for management candidates of {}", Iterables.toString(addresses), location);
    +        for (String address : addresses) {
    +            if (isAddressResolvable(address)) {
    +                return ImmutableList.of(HostAndPort.fromParts(address, defaultPort));
    +            }
    +        }
    +
    +        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
    +                new Object[]{addresses, location.getCreationString(config), node});
    +        String host = Iterables.getFirst(addresses, null);
    +        if (host != null) {
    +            return ImmutableList.of(HostAndPort.fromParts(host, defaultPort));
    +        } else {
    +            return ImmutableList.of();
    +        }
    +    }
    +
    +    /**
    +     * Returns all reachable addresses according to reachablePredicate.
    +     * Iterators are ordered according to the configured {@link #getMode() mode}.
    +     */
    +    protected Iterable<HostAndPort> getReachableAddresses(NodeMetadata node, Predicate<? super HostAndPort> reachablePredicate, Duration timeout) {
    +        if (timeout == null) timeout = Duration.FIVE_MINUTES;
    +        Iterable<String> candidates = getNodeAddressesWithMode(node);
    +        return JcloudsUtil.getReachableAddresses(candidates, node.getLoginPort(), timeout, reachablePredicate);
    +    }
    +
    +    protected Iterable<String> getNodeAddressesWithMode(NodeMetadata node) {
    +        switch (getMode()) {
    +        case ONLY_PRIVATE:
    +            return node.getPrivateAddresses();
    +        case ONLY_PUBLIC:
    +            return node.getPublicAddresses();
    +        case PREFER_PRIVATE:
    +            return Iterables.concat(node.getPrivateAddresses(), node.getPublicAddresses());
    +        case PREFER_PUBLIC:
    +        default:
    +            return Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses());
    +        }
    +    }
    +
    +    protected boolean isAddressResolvable(String addr) {
    +        try {
    +            Networking.getInetAddressWithFixedName(addr);
    +            return true; // fine, it resolves
    +        } catch (RuntimeException e) {
    +            Exceptions.propagateIfFatal(e);
    +            return false;
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    protected boolean shouldCheckCredentials() {
    +        return Boolean.TRUE.equals(config().get(CHECK_CREDENTIALS));
    +    }
    +
    +    protected boolean checkCredential(
    +            JcloudsLocation location, HostAndPort hostAndPort, LoginCredentials credentials,
    +            ConfigBag config, boolean isWindows) {
    +        try {
    +            if (isWindows) {
    +                location.waitForWinRmAvailable(credentials, hostAndPort, config);
    +            } else {
    +                location.waitForSshable(hostAndPort, ImmutableList.of(credentials), config);
    +            }
    +            return true;
    +        } catch (IllegalStateException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected Iterable<LoginCredentials> getCredentialCandidates(
    +            JcloudsLocation location, NodeMetadata node, ManagementAddressResolveOptions options, ConfigBag setup) {
    +        LoginCredentials userCredentials = null;
    +        // Figure out which login credentials to use. We only make a connection with
    +        // initialCredentials when jclouds didn't do any sshing and wait for connectable is true.
    +        // 0. if jclouds didn't do anything and we should wait for the machine then initial credentials is
    +        //    whatever waitForSshable determines and then create the user ourselves.
    +        if (options.skipJcloudsSshing() && options.expectReachable()) {
    +            if (options.isWindows()) {
    +                return ImmutableList.of(options.getInitialCredentials());
    +            } else {
    +                return location.generateCredentials(node.getCredentials(), setup.get(JcloudsLocationConfig.LOGIN_USER));
    +            }
    +        }
    +
    +        // 1. Were they configured by the user?
    +        LoginCredentials customCredentials = setup.get(JcloudsLocation.CUSTOM_CREDENTIALS);
    --- End diff --
    
    `CUSTOM_CREDENTIALS` is actually in `JcloudsLocationConfig` and `JcloudsLocation` extends it - better to reference statics with the class that declares them. Here and elsewhere in this class.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97325417
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    --- End diff --
    
    Suggest adding in some text here to describe what it does - just a copy of  the description from the PR would be great.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97971034
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/ManagementAddressResolveOptions.java ---
    @@ -0,0 +1,158 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.domain.LoginCredentials;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * Holds parameters to be used by a {@link LocationNetworkInfoCustomizer}.
    + */
    +@Beta
    +public class ManagementAddressResolveOptions {
    +
    +    private Duration pollTimeout;
    +    private boolean waitForSshable;
    +    private boolean pollForFirstReachableAddress;
    +    private boolean expectReachable;
    +    private boolean isWindows;
    +    private boolean propagatePollForReachableFailure;
    +    private LoginCredentials initialCredentials;
    +    private Optional<LoginCredentials> userCredentials = Optional.absent();
    +    private Optional<HostAndPort> hostAndPortOverride = Optional.absent();
    +    private boolean skipJcloudsSshing;
    +    private boolean publishNetworkSensors = true;
    +    private Predicate<? super HostAndPort> reachableAddressPredicate;
    +
    +    public ManagementAddressResolveOptions expectReachable(boolean expectConnectable) {
    +        this.expectReachable = expectConnectable;
    +        return this;
    +    }
    +
    +    /** Indicate the host and port that should be used over all others. Normally used in tandem with a port forwarder. */
    +    public ManagementAddressResolveOptions hostAndPortOverride(Optional<HostAndPort> hostAndPortOverride) {
    +        this.hostAndPortOverride = hostAndPortOverride;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions initialCredentials(LoginCredentials initialCredentials) {
    +        this.initialCredentials = initialCredentials;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions pollForFirstReachableAddress(boolean pollForFirstReachableAddress) {
    +        this.pollForFirstReachableAddress = pollForFirstReachableAddress;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions propagatePollForReachableFailure(boolean propagatePollForReachableFailure) {
    +        this.propagatePollForReachableFailure = propagatePollForReachableFailure;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions pollTimeout(Duration pollTimeout) {
    +        this.pollTimeout = pollTimeout;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions skipJcloudsSshing(boolean skipJcloudsSshing) {
    +        this.skipJcloudsSshing = skipJcloudsSshing;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions waitForSshable(boolean waitForSshable) {
    +        this.waitForSshable = waitForSshable;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions isWindows(boolean windows) {
    +        isWindows = windows;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions userCredentials(Optional<LoginCredentials> userCredentials) {
    +        this.userCredentials = userCredentials;
    +        return this;
    +    }
    +
    +    public ManagementAddressResolveOptions publishNetworks(boolean publishNetworks) {
    --- End diff --
    
    Setter is `publishNetworks`, while getter is `publishNetworkSensor`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by sjcorbett <gi...@git.apache.org>.
Github user sjcorbett commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97603990
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -625,6 +630,30 @@ public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableExceptio
             }
         }
     
    +    protected ManagementAddressResolveOptions getManagementAddressResolveOptions(
    +            NodeMetadata node, ConfigBag setup, Optional<HostAndPort> sshHostAndPortOverride, Optional<LoginCredentials> userCredentials) {
    +        boolean waitForSshable = !"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
    --- End diff --
    
    The config keys it needs are only available as arguments to certain methods (`obtain`, primarily) so we can't pull it up to the constructor.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97357864
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/ManagementAddressResolveOptions.java ---
    @@ -0,0 +1,147 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.domain.LoginCredentials;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Optional;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * Holds parameters to be used by a {@link LocationNetworkInfoCustomizer}.
    + */
    +@Beta
    +public class ManagementAddressResolveOptions {
    +
    +    private Duration pollTimeout;
    +    private boolean waitForSshable;
    +    private boolean pollForFirstReachableAddress;
    --- End diff --
    
    Could also add `POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE` and `POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE`, keeping all access to these closely related keys together. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97346292
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---
    @@ -2607,24 +2522,32 @@ public boolean apply(@Nullable WinRmMachineLocation machine) {
             return credsSuccessful.get();
         }
     
    -    protected LoginCredentials waitForSshable(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    -        LoginCredentials nodeCreds = node.getCredentials();
    +    protected LoginCredentials waitForSshableGuessCredentials(final ComputeService computeService, final NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        // See https://issues.apache.org/jira/browse/BROOKLYN-186
    +        // Handle where jclouds gives us the wrong login user (!) and both a password + ssh key.
    +        // Try all the permutations to find the one that works.
    +        Iterable<LoginCredentials> credentialsToTry = generateCredentials(node.getCredentials(), setup.get(LOGIN_USER));
    +        return waitForSshable(computeService, node, managementHostAndPort, credentialsToTry, setup);
    +    }
    +
    +    /** @deprecated Since 0.11.0. Use {@link #waitForSshableGuessCredentials} instead. */
    +    @Deprecated
    +    protected LoginCredentials waitForSshable(ComputeService computeService, NodeMetadata node, HostAndPort managementHostAndPort, ConfigBag setup) {
    +        return waitForSshable(computeService, node, managementHostAndPort, setup);
    --- End diff --
    
    Is this not recursive? Should be `waitForSshableGuessCredentials` I suppose.  This isn't hit in the unit tests, can we add one that will hit it?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97986528
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && options.getReachableAddressPredicate() != null) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                final Predicate<? super HostAndPort> predicate = options.getReachableAddressPredicate();
    +                return getReachableAddresses(node, predicate, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        } else if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.warn("{} was configured to expect node {} to be reachable and to poll for its first reachable " +
    +                    "address but the predicate to determine reachability was null", this, node);
    +        }
    +
    +        Iterable<String> addresses = getNodeAddressesWithMode(node);
    +        LOG.debug("Using first resolvable address in {} for management candidates of {}", Iterables.toString(addresses), location);
    +        for (String address : addresses) {
    +            if (isAddressResolvable(address)) {
    --- End diff --
    
    Can you add logging for the `else` case. Would be really interested in what kind of addresses would fail the check, given all of them are expected to be IPs.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97359572
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/ManagementAddressResolveOptions.java ---
    @@ -0,0 +1,147 @@
    +/*
    + * 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.jclouds;
    +
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.domain.LoginCredentials;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Optional;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * Holds parameters to be used by a {@link LocationNetworkInfoCustomizer}.
    + */
    +@Beta
    +public class ManagementAddressResolveOptions {
    +
    +    private Duration pollTimeout;
    +    private boolean waitForSshable;
    +    private boolean pollForFirstReachableAddress;
    +    private boolean expectReachable;
    +    private boolean isWindows;
    +    private boolean propagatePollForReachableFailure;
    +    private LoginCredentials initialCredentials;
    --- End diff --
    
    Could add JcloudsLocation.CUSTOM_CREDENTIALS


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97988039
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    --- End diff --
    
    Some suggestions how to make the logic more streamlined (less branching combinations):
      * The old impl of `resolveManagementHostAndPort` always returned the override pair. I think we should keep that. No point in checking whether the credentials work for it - if not then we need to return it nevertheless. In that case what's the point of passing it in? Let the caller do the check?
      * Move this check higher (at least the `expectReachable`), just above `managementCandidates`. Then splitting `getManagementCandidates` to: 
        * leave only the getHostnameAws & getReachableAddresses cases (override removed above), removing the corresponding guarding checks
        * moving the fallback cases to another method and using them in the `hapChoice == null` case.
    
    As mentioned elsewhere even `getHostnameAws` better be moved at the end of this method, once we have identified the endpoint+credentials to connect to the machine. So this will leave `getManagementCandidates` just a delegate to `getReachableAddresses`.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by neykov <gi...@git.apache.org>.
Github user neykov commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97989834
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,472 @@
    +/*
    + * 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.jclouds;
    +
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityInitializer;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * BasicLocationNetworkInfoCustomizer provides the default implementation of
    + * {@link LocationNetworkInfoCustomizer}. It exposes options to have JcloudsLocation
    + * prefer to contact VMs on private addresses and can be injected on a
    + * per-entity basis. For example:
    + * <pre>
    + * services:
    + * - type: server
    + *   location: the-same-private-network-as-brooklyn
    + *   brooklyn.initializers:
    + *   - type: org.apache.brooklyn.location.jclouds.BasicLocationNetworkInfoCustomizer
    + *     brooklyn.config:
    + *       mode: ONLY_PRIVATE
    + * - type: server
    + *   location: another-cloud
    + *   # implicit use of PREFER_PUBLIC.
    + * </pre>
    + * Would result in the first entity being managed on the instance's private address (and deployment
    + * failing if this was not possible) and the second being managed on its public address. Graceful
    + * fallback is possible by replacing ONLY_PRIVATE with PREFER_PRIVATE. There are PUBLIC variants of
    + * each of these.
    + * <p>
    + * BasicLocationNetworkInfoCustomizer is the default location network info customizer used by
    + * {@link JcloudsLocation} when {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * is unset.
    + * <p>
    + * When used as an {@link EntityInitializer} the instance inserts itself into the entity's
    + * provisioning properties under the {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER}
    + * subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> CHECK_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "checkCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customizer should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldCheckCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (checkCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    --- End diff --
    
    You are missing the resolvable check, see above comment.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] brooklyn-server pull request #529: LocationNetworkInfoCustomizer

Posted by geomacy <gi...@git.apache.org>.
Github user geomacy commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/529#discussion_r97512526
  
    --- Diff: locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/BasicLocationNetworkInfoCustomizer.java ---
    @@ -0,0 +1,473 @@
    +/*
    + * 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.jclouds;
    +
    +import java.lang.reflect.InvocationTargetException;
    +import java.util.Iterator;
    +import java.util.Map;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +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.config.ConfigUtils;
    +import org.apache.brooklyn.core.entity.Attributes;
    +import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
    +import org.apache.brooklyn.core.location.LocationConfigKeys;
    +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
    +import org.apache.brooklyn.util.collections.MutableMap;
    +import org.apache.brooklyn.util.core.config.ConfigBag;
    +import org.apache.brooklyn.util.core.task.Tasks;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.time.Duration;
    +import org.jclouds.compute.domain.NodeMetadata;
    +import org.jclouds.domain.LoginCredentials;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.MoreObjects;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Stopwatch;
    +import com.google.common.base.Supplier;
    +import com.google.common.base.Suppliers;
    +import com.google.common.collect.ImmutableList;
    +import com.google.common.collect.Iterables;
    +import com.google.common.net.HostAndPort;
    +
    +/**
    + * The default location network info customizer.
    + * <p>
    + * When used as an {@link org.apache.brooklyn.api.entity.EntityInitializer} the
    + * instance inserts itself into the entity's provisioning properties under the
    + * {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER} subkey.
    + * <p>
    + * This class is annotated @Beta and is likely to change in the future.
    + */
    +@Beta
    +public class BasicLocationNetworkInfoCustomizer extends BasicJcloudsLocationCustomizer implements LocationNetworkInfoCustomizer {
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(BasicLocationNetworkInfoCustomizer.class);
    +
    +    public enum NetworkMode {
    +        /**
    +         * Check each node's {@link NodeMetadata#getPublicAddresses() public addresses}
    +         * for reachability before its {@link NodeMetadata#getPrivateAddresses() private addresses}.
    +         */
    +        PREFER_PUBLIC,
    +        /**
    +         * Check each node's {@link NodeMetadata#getPrivateAddresses() private addresses}
    +         * for reachability before its {@link NodeMetadata#getPublicAddresses() public addresses}.
    +         */
    +        PREFER_PRIVATE,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPublicAddresses() public addresses} for reachability.
    +         */
    +        ONLY_PUBLIC,
    +        /**
    +         * Check only a node's {@link NodeMetadata#getPrivateAddresses()}  private addresses} for reachability.
    +         */
    +        ONLY_PRIVATE
    +    }
    +
    +    public static final ConfigKey<NetworkMode> MODE = ConfigKeys.newConfigKey(NetworkMode.class,
    +            "mode", "Operation mode", NetworkMode.PREFER_PUBLIC);
    +
    +    @Beta
    +    public static final ConfigKey<Boolean> TEST_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
    +            "testCredentials",
    +            "Indicates that credentials should be tested when determining endpoint reachability.",
    +            Boolean.TRUE);
    +
    +    public static final ConfigKey<Boolean> PUBLISH_NETWORKS = ConfigKeys.newBooleanConfigKey(
    +            "publishNetworks",
    +            "Indicates that the customiser should publish addresses as sensors on each entity",
    +            Boolean.TRUE);
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    public BasicLocationNetworkInfoCustomizer() {
    +        super();
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(Map<?, ?> params) {
    +        super(params);
    +    }
    +
    +    public BasicLocationNetworkInfoCustomizer(final ConfigBag params) {
    +        super(params);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Overrides the behaviour of {@link BasicJcloudsLocationCustomizer#apply(EntityLocal)} to set
    +     * the instance as the value of {@link JcloudsLocationConfig#LOCATION_NETWORK_INFO_CUSTOMIZER},
    +     * rather than in its provisioning properties.
    +     */
    +    @Override
    +    public void apply(EntityLocal entity) {
    +        ConfigKey<Object> subkey = BrooklynConfigKeys.PROVISIONING_PROPERTIES.subKey(JcloudsLocationConfig.LOCATION_NETWORK_INFO_CUSTOMIZER.getName());
    +        entity.config().set(subkey, this);
    +        LOG.debug("{} set itself as the location network info customizer on {}", this, entity);
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Combines the given resolve options with the customiser's configuration to determine the
    +     * best address and credential pair for management. In particular, if the resolve options
    +     * allow it will check that the credential is actually valid for the address.
    +     */
    +    @Override
    +    public ManagementAddressResolveResult resolve(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        LOG.debug("{} resolving management parameters for {}, node={}, config={}, options={}",
    +                new Object[]{this, location, node, config, options});
    +        Stopwatch timer = Stopwatch.createStarted();
    +        // Should only be null in tests.
    +        final Entity contextEntity = getContextEntity(config);
    +        if (shouldPublishNetworks() && options.publishNetworkSensors() && contextEntity != null) {
    +            publishNetworks(node, contextEntity);
    +        }
    +        HostAndPort hapChoice = null;
    +        LoginCredentials credChoice = null;
    +
    +        Iterable<HostAndPort> managementCandidates = getManagementCandidates(location, node, config, options);
    +        Iterable<LoginCredentials> credentialCandidates = getCredentialCandidates(location, node, options, config);
    +
    +        // Try each pair of address and credential until one succeeds.
    +        if (options.expectReachable() && options.pollForFirstReachableAddress() && shouldTestCredentials()) {
    +            for (HostAndPort hap : managementCandidates) {
    +                for (LoginCredentials cred : credentialCandidates) {
    +                    LOG.trace("Testing host={} with credential={}", hap, cred);
    +                    if (testCredential(location, hap, cred, config, options.isWindows())) {
    +                        hapChoice = hap;
    +                        credChoice = cred;
    +                        break;
    +                    }
    +                }
    +                if (hapChoice != null) break;
    +            }
    +        }
    +
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first management candidate given node={} and mode={}", node, getMode());
    +            hapChoice = Iterables.getFirst(managementCandidates, null);
    +        }
    +        if (hapChoice == null) {
    +            LOG.trace("Choosing first address of node={} in mode={}", node, getMode());
    +            final Iterator<String> hit = getNodeAddressesWithMode(node).iterator();
    +            if (hit.hasNext()) HostAndPort.fromHost(hit.next());
    +        }
    +        if (hapChoice == null) {
    +            throw new IllegalStateException("Exhausted all options when determining address for " + location);
    +        }
    +
    +        if (credChoice == null) {
    +            credChoice = Iterables.getFirst(credentialCandidates, null);
    +            if (credChoice == null) {
    +                throw new IllegalStateException("Exhausted all options when determining credential for " + location);
    +            }
    +        }
    +
    +        if (contextEntity != null) {
    +            contextEntity.sensors().set(Attributes.ADDRESS, hapChoice.getHostText());
    +        }
    +        ManagementAddressResolveResult result = new ManagementAddressResolveResult(hapChoice, credChoice);
    +        LOG.debug("{} resolved management parameters for {} in {}: {}",
    +                new Object[]{this, location, Duration.of(timer), result});
    +        return result;
    +    }
    +
    +    private boolean shouldPublishNetworks() {
    +        return Boolean.TRUE.equals(config().get(PUBLISH_NETWORKS));
    +    }
    +
    +    // TODO: Separate this into second part?
    +    void publishNetworks(NodeMetadata node, Entity entity) {
    +        // todo hostnames?
    +        int i = 0;
    +        for (String address : node.getPrivateAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.private." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +        i = 0;
    +        for (String address : node.getPublicAddresses()) {
    +            final AttributeSensor<String> sensor = Sensors.newStringSensor("host.address.public." + i++);
    +            if (entity.sensors().get(sensor) == null) {
    +                entity.sensors().set(sensor, address);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    /**
    +     * Returns the hosts and ports that should be considered when determining the address
    +     * to use when connecting to the location by assessing the following criteria:
    +     * <ol>
    +     *     <li>Use the hostAndPortOverride set in options.</li>
    +     *     <li>If the machine is connectable, user credentials are given and the machine is provisioned
    +     *     in AWS then use {@link JcloudsLocation#getHostnameAws(NodeMetadata, Optional, Supplier, ConfigBag)}.</li>
    +     *     <li>If the machine is connectable and pollForFirstReachableAddress is set in options then use all
    +     *     {@link #getReachableAddresses reachable} addresses.</li>
    +     *     <li>Use the first address that is resolvable with {@link #isAddressResolvable}.</li>
    +     *     <li>Use the first address in the node's public then private addresses.</li>
    +     * </ol>
    +     */
    +    protected Iterable<HostAndPort> getManagementCandidates(
    +            JcloudsLocation location, NodeMetadata node, ConfigBag config, ManagementAddressResolveOptions options) {
    +        final Optional<HostAndPort> hostAndPortOverride = options.getHostAndPortOverride();
    +        boolean lookupAwsHostname = Boolean.TRUE.equals(config.get(JcloudsLocation.LOOKUP_AWS_HOSTNAME));
    +        String provider = config.get(JcloudsLocation.CLOUD_PROVIDER);
    +        if (provider == null) provider = location.getProvider();
    +        int defaultPort;
    +        if (options.isWindows()) {
    +            defaultPort = config.get(WinRmMachineLocation.USE_HTTPS_WINRM) ? 5986 : 5985;
    +        } else {
    +            defaultPort = node.getLoginPort();
    +        }
    +
    +        // Will normally have come from port forwarding.
    +        if (hostAndPortOverride.isPresent()) {
    +            // Don't try to resolve it; just use it
    +            int port = hostAndPortOverride.get().hasPort()
    +                       ? hostAndPortOverride.get().getPort()
    +                       : defaultPort;
    +            final HostAndPort override = HostAndPort.fromParts(hostAndPortOverride.get().getHostText(), port);
    +            LOG.debug("Using host and port override for management candidates of {}: {}", location, override);
    +            return ImmutableList.of(override);
    +        }
    +
    +        // Treat AWS as a special case because the DNS fully qualified hostname in AWS is
    +        // (normally?!) a good way to refer to the VM from both inside and outside of the region.
    +        // TODO This is a bit weird: if the statement below is true then getHostnameAws will find the first
    +        // reachable address, which repeats if case after this one.
    +        if (options.expectReachable() && options.getUserCredentials().isPresent() && "aws-ec2".equals(provider) && lookupAwsHostname) {
    +            // getHostnameAws sshes to the machine and curls 169.254.169.254/latest/meta-data/public-hostname.
    +            Maybe<String> result = location.getHostnameAws(
    +                    node, Optional.<HostAndPort>absent(), Suppliers.ofInstance(options.getUserCredentials().get()), config);
    +            if (result.isPresent()) {
    +                LOG.debug("Resolved AWS hostname for management candidates of {}: {}", location, result.get());
    +                return ImmutableList.of(HostAndPort.fromParts(result.get(), defaultPort));
    +            }
    +        }
    +        if (options.expectReachable() && options.pollForFirstReachableAddress()) {
    +            LOG.debug("Using reachable addresses for management candidates of {}", location);
    +            try {
    +                return getReachableAddresses(node, config, options.getPollTimeout());
    +            } catch (RuntimeException e) {
    +                if (options.propagatePollForReachableFailure()) {
    +                    throw Exceptions.propagate(e);
    +                } else {
    +                    LOG.warn("No reachable address ({}/{}); falling back to any advertised address; may cause future failures",
    +                            location.getCreationString(config), node);
    +                }
    +            }
    +        }
    +
    +        Iterable<String> addresses = getNodeAddressesWithMode(node);
    +        LOG.debug("Using first resolvable address in {} for management candidates of {}", Iterables.toString(addresses), location);
    +        for (String address : addresses) {
    +            if (isAddressResolvable(address)) {
    +                return ImmutableList.of(HostAndPort.fromParts(address, defaultPort));
    +            }
    +        }
    +
    +        LOG.warn("No resolvable address in {} ({}/{}); using first; may cause future failures",
    +                new Object[]{addresses, location.getCreationString(config), node});
    +        String host = Iterables.getFirst(addresses, null);
    +        if (host != null) {
    +            return ImmutableList.of(HostAndPort.fromParts(host, defaultPort));
    +        } else {
    +            return ImmutableList.of();
    +        }
    +    }
    +
    +    /**
    +     * Returns all reachable addresses according to {@link #getReachableAddressesPredicate}.
    +     * Iterators are ordered according to the configured {@link #getMode() mode}.
    +     */
    +    protected Iterable<HostAndPort> getReachableAddresses(NodeMetadata node, ConfigBag setup, Duration timeout) {
    +        if (timeout == null) timeout = Duration.FIVE_MINUTES;
    +        Iterable<String> candidates = getNodeAddressesWithMode(node);
    +        Predicate<? super HostAndPort> pollForFirstReachableHostAndPortPredicate = getReachableAddressesPredicate(setup);
    +        return JcloudsUtil.getReachableAddresses(candidates, node.getLoginPort(), timeout, pollForFirstReachableHostAndPortPredicate);
    +    }
    +
    +    protected Iterable<String> getNodeAddressesWithMode(NodeMetadata node) {
    +        switch (getMode()) {
    +        case ONLY_PRIVATE:
    +            return node.getPrivateAddresses();
    +        case ONLY_PUBLIC:
    +            return node.getPublicAddresses();
    +        case PREFER_PRIVATE:
    +            return Iterables.concat(node.getPrivateAddresses(), node.getPublicAddresses());
    +        case PREFER_PUBLIC:
    +        default:
    +            return Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses());
    +        }
    +    }
    +
    +    protected boolean isAddressResolvable(String addr) {
    +        try {
    +            Networking.getInetAddressWithFixedName(addr);
    +            return true; // fine, it resolves
    +        } catch (RuntimeException e) {
    +            Exceptions.propagateIfFatal(e);
    +            return false;
    +        }
    +    }
    +
    +    protected Predicate<? super HostAndPort> getReachableAddressesPredicate(ConfigBag setup) {
    +        if (setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE) != null) {
    +            LOG.debug("{} polling for first reachable address with {}",
    +                    this, setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE));
    +            return setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE);
    +        } else {
    +            LOG.debug("{} polling for first reachable address with instance of {}",
    +                    this, JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE.getName());
    +
    +            Class<? extends Predicate<? super HostAndPort>> predicateType =
    +                    setup.get(JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE);
    +
    +            Map<String, Object> args = MutableMap.of();
    +            ConfigUtils.addUnprefixedConfigKeyInConfigBack(
    +                    JcloudsLocation.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".", setup, args);
    +            try {
    +                return predicateType.getConstructor(Map.class).newInstance(args);
    +            } catch (NoSuchMethodException | IllegalAccessException e) {
    +                try {
    +                    return predicateType.newInstance();
    +                } catch (IllegalAccessException | InstantiationException newInstanceException) {
    +                    throw Exceptions.propagate("Instantiating " + predicateType + " failed.", newInstanceException);
    +                }
    +            } catch (InvocationTargetException | InstantiationException e) {
    +                throw Exceptions.propagate("Problem trying to instantiate " + predicateType + " with Map constructor.", e);
    +            }
    +        }
    +    }
    +
    +    // --------------------------------------------------------------------------------------
    +
    +    protected boolean shouldTestCredentials() {
    +        return Boolean.TRUE.equals(config().get(TEST_CREDENTIALS));
    +    }
    +
    +    protected boolean testCredential(
    +            JcloudsLocation location, HostAndPort hostAndPort, LoginCredentials credentials,
    +            ConfigBag config, boolean isWindows) {
    +        try {
    +            if (isWindows) {
    +                location.waitForWinRmAvailable(credentials, hostAndPort, config);
    +            } else {
    +                location.waitForSshable(hostAndPort, ImmutableList.of(credentials), config);
    +            }
    +            return true;
    +        } catch (IllegalStateException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected Iterable<LoginCredentials> getCredentialCandidates(
    +            JcloudsLocation location, NodeMetadata node, ManagementAddressResolveOptions options, ConfigBag setup) {
    +        LoginCredentials userCredentials = null;
    +        // Figure out which login credentials to use. We only make a connection with
    +        // initialCredentials when jclouds didn't do any sshing and wait for connectable is true.
    +        // 0. if jclouds didn't do anything and we should wait for the machine then initial credentials is
    +        //    whatever waitForSshable determines and then create the user ourselves.
    +        if (options.skipJcloudsSshing() && options.expectReachable()) {
    +            if (options.isWindows()) {
    +                return ImmutableList.of(options.getInitialCredentials());
    +            } else {
    +                return location.generateCredentials(node.getCredentials(), setup.get(JcloudsLocationConfig.LOGIN_USER));
    +            }
    +        }
    +
    +        // 1. Were they configured by the user?
    +        LoginCredentials customCredentials = setup.get(JcloudsLocation.CUSTOM_CREDENTIALS);
    +        if (customCredentials != null) {
    +            userCredentials = customCredentials;
    +            //set userName and other data, from these credentials
    +            Object oldUsername = setup.put(JcloudsLocation.USER, customCredentials.getUser());
    +            LOG.debug("Using username {}, from custom credentials, on node {}. User was previously {}",
    +                    new Object[]{customCredentials.getUser(), node, oldUsername});
    +            if (customCredentials.getOptionalPassword().isPresent()) {
    +                setup.put(JcloudsLocation.PASSWORD, customCredentials.getOptionalPassword().get());
    +            }
    +            if (customCredentials.getOptionalPrivateKey().isPresent()) {
    +                setup.put(JcloudsLocation.PRIVATE_KEY_DATA, customCredentials.getOptionalPrivateKey().get());
    +            }
    +        }
    +        // 2. Can they be extracted from the setup+node?
    +        if (userCredentials == null ||
    +                (!userCredentials.getOptionalPassword().isPresent() && !userCredentials.getOptionalPrivateKey().isPresent())) {
    +            // We either don't have any userCredentials, or it is missing both a password/key.
    --- End diff --
    
    This sounds like a misconfiguration, so it's probably worth putting a WARN in the log.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---