You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@brooklyn.apache.org by aledsage <gi...@git.apache.org> on 2016/06/01 22:55:05 UTC

[GitHub] brooklyn-server pull request #177: Adds PublicNetworkFaceEnricher

GitHub user aledsage opened a pull request:

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

    Adds PublicNetworkFaceEnricher

    

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

    $ git pull https://github.com/aledsage/brooklyn-server feature/PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177.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 #177
    
----
commit 6c3f378f17face2ca7d5f395a92dc35c94da720c
Author: Aled Sage <al...@gmail.com>
Date:   2016-06-01T22:51:55Z

    Adds PublicNetworkFaceEnricher

----


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r66324459
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    --- End diff --
    
    Yes, I think we will be deprecating this method (i.e. the non-default PFM, or maybe even the PFM) in light of the "Working with multipe networks" proposal.
    
    A PFM just needs to be scoped such that there will not be clashes in the "publicIpId". That field came about from the early days of doing cloudstack integration. It's not really appropriate in many cases. In advanced networking use-cases, we've created a PFM for the "subnet" (which generally meant for the app, or a group of apps that we were carefully including as children of a single SubnetTier entity.
    
    For now, I think we should assume "always using the default". I'm tempted to remove that as a config key from the enricher. But given it's a config key on other things, then it seemed sensible to do the same here. So I'll just leave it as-is for now. We can deprecate config/use of the PFM across the entire code-base if we decide to do that.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65462479
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    --- End diff --
    
    Not convinced we'd ever use `Long` as a type for a port sensor. And `Short` doesn't even have the full range - why not add `Character` if we're doing this, since it's the only type that accurately represents a port number, being a 16 bit unsigned value from 0-65535? 


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65909445
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                // TODO What if publicTarget is still null, but will be set soon? We're not subscribed to changes in the PortForwardManager!
    +                // TODO Should we return null or sensorVal? In this method we always return sensorVal;
    +                //      but in HostAndPortTransformingEnricher we always return null!
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    +            } catch (URISyntaxException e) {
    +                LOG.debug("Error transforming URI "+uri+", using target "+publicTarget+"; rethrowing");
    +                throw Exceptions.propagateAnnotated("Error transforming URI "+uri+", using target "+publicTarget, e);
    +            }
    +            return Maybe.of(result.toString());
    +        } else {
    +            LOG.debug("sensor mapper not transforming URI "+uri+" because no port defined");
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformHostAndPort(Entity source, MachineLocation machine, String sensorVal) {
    +        HostAndPort hostAndPort = HostAndPort.fromString(sensorVal);
    +        if (hostAndPort.hasPort()) {
    +            int port = hostAndPort.getPort();
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} host-and-port {} because defines no port", source, hostAndPort);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformPort(Entity source, MachineLocation machine, int sensorVal) {
    +        if (Networking.isPortValid(sensorVal)) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, sensorVal);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} port {} because not a valid port", source, sensorVal);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<MachineLocation> getMachine() {
    +        return Machines.findUniqueMachineLocation(entity.getLocations());
    +    }
    +    
    +    protected PortForwardManager getPortForwardManager() {
    +        PortForwardManager portForwardManager = config().get(PORT_FORWARD_MANAGER);
    +        if (portForwardManager == null) {
    +            portForwardManager = (PortForwardManager) getManagementContext().getLocationRegistry().getLocationManaged("portForwardManager(scope=global)");
    --- End diff --
    
    `portForwardManager(scope=global)` is used at so many places now, that it deserves to be promoted to a constant.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65985630
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                // TODO What if publicTarget is still null, but will be set soon? We're not subscribed to changes in the PortForwardManager!
    +                // TODO Should we return null or sensorVal? In this method we always return sensorVal;
    +                //      but in HostAndPortTransformingEnricher we always return null!
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    +            } catch (URISyntaxException e) {
    +                LOG.debug("Error transforming URI "+uri+", using target "+publicTarget+"; rethrowing");
    +                throw Exceptions.propagateAnnotated("Error transforming URI "+uri+", using target "+publicTarget, e);
    +            }
    +            return Maybe.of(result.toString());
    +        } else {
    +            LOG.debug("sensor mapper not transforming URI "+uri+" because no port defined");
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformHostAndPort(Entity source, MachineLocation machine, String sensorVal) {
    +        HostAndPort hostAndPort = HostAndPort.fromString(sensorVal);
    +        if (hostAndPort.hasPort()) {
    +            int port = hostAndPort.getPort();
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} host-and-port {} because defines no port", source, hostAndPort);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformPort(Entity source, MachineLocation machine, int sensorVal) {
    +        if (Networking.isPortValid(sensorVal)) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, sensorVal);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} port {} because not a valid port", source, sensorVal);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<MachineLocation> getMachine() {
    +        return Machines.findUniqueMachineLocation(entity.getLocations());
    +    }
    +    
    +    protected PortForwardManager getPortForwardManager() {
    +        PortForwardManager portForwardManager = config().get(PORT_FORWARD_MANAGER);
    +        if (portForwardManager == null) {
    +            portForwardManager = (PortForwardManager) getManagementContext().getLocationRegistry().getLocationManaged("portForwardManager(scope=global)");
    --- End diff --
    
    +1 suggested this in #181 as well


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65905742
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    --- End diff --
    
    Aren't PFM's set on the entity? What if we use this enricher in clocker context.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r66330364
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    +            } else {
    +                return input + ".mapped." + network;
    +            }
    +        }
    +    }
    +
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected Optional<Predicate<Sensor<?>>> mapMatching;
    +    protected Function<? super String, String> sensorNameConverter;
    +    protected PortForwardManager.AssociationListener pfmListener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        checkConfig();
    +        sensors = resolveSensorsConfig();
    +        if (sensors.isEmpty()) {
    +            mapMatching = Optional.of(resolveMapMatchingConfig());
    +        } else {
    +            mapMatching = Optional.absent();
    +        }
    +        sensorNameConverter = getRequiredConfig(SENSOR_NAME_CONVERTER);
    +        
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        pfmListener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {OnPublicNetworkEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(pfmListener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {OnPublicNetworkEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +        if (mapMatching.isPresent()) {
    +            Sensor<?> wildcardSensor = null;
    +            subscriptions().subscribe(entity, wildcardSensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    if (mapMatching.get().apply(event.getSensor())) {
    +                        LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                                new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                        tryTransform((AttributeSensor<?>)event.getSensor());
    +                    }
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (pfmListener != null) {
    +                getPortForwardManager().removeAssociationListener(pfmListener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +        if (mapMatching.isPresent()) {
    +            for (Sensor<?> sensor : entity.getEntityType().getSensors()) {
    +                if (sensor instanceof AttributeSensor && mapMatching.get().apply(sensor)) {
    +                    try {
    +                        tryTransform(machine.get(), (AttributeSensor<?>)sensor);
    +                    } catch (Exception e) {
    +                        // TODO Avoid repeated logging
    +                        Exceptions.propagateIfFatal(e);
    +                        LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +                    }
    +                }
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    --- End diff --
    
    I wondered about that. It depends why the port-mapping was removed from the PFM. If it's because the entity is stopping, then the enricher shouldn't bother trying to keep up with that. But if it's because the the mappings are more dynamic, then it would make sense to remove it.
    
    My reasoning was that we don't delete the main.uri sensor for a Tomcat entity when the tomcat process stops, so we can similarly leave these sensors as-is.
    
    But like I said, if things are a lot more dynamic for DNAT rules added/removed on-the-fly then we should revisit.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177
  
    @neykov @grkvlt @ahgittin I've incorporated those comments - can you take another look please?


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65801453
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    --- End diff --
    
    Why not `JoinPublicNetworkEnricher`?


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65670186
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    --- End diff --
    
    i'd check that `toString.trim()` matches regex `[0-9]+`, just in case the port is a string number.  that's unambiguously not a URL so we're fine.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65986057
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    --- End diff --
    
    @aledsage I'd like to have some logic that determines the type based on the sensor name suffix, i.e. `_thing_.url` and `_thing_.url` and _then_ checks whether the value is appropriate for the name. And, if the name doesn't match any of the suffixes we just use the type of the value like this.


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65911936
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                // TODO What if publicTarget is still null, but will be set soon? We're not subscribed to changes in the PortForwardManager!
    --- End diff --
    
    I thought that's what `onAssociationCreated` is about.


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66236021
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    +            } else {
    +                return input + ".mapped." + network;
    +            }
    +        }
    +    }
    +
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected Optional<Predicate<Sensor<?>>> mapMatching;
    +    protected Function<? super String, String> sensorNameConverter;
    +    protected PortForwardManager.AssociationListener pfmListener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        checkConfig();
    +        sensors = resolveSensorsConfig();
    +        if (sensors.isEmpty()) {
    +            mapMatching = Optional.of(resolveMapMatchingConfig());
    +        } else {
    +            mapMatching = Optional.absent();
    +        }
    +        sensorNameConverter = getRequiredConfig(SENSOR_NAME_CONVERTER);
    +        
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        pfmListener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {OnPublicNetworkEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(pfmListener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {OnPublicNetworkEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +        if (mapMatching.isPresent()) {
    +            Sensor<?> wildcardSensor = null;
    +            subscriptions().subscribe(entity, wildcardSensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    if (mapMatching.get().apply(event.getSensor())) {
    +                        LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                                new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                        tryTransform((AttributeSensor<?>)event.getSensor());
    +                    }
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (pfmListener != null) {
    +                getPortForwardManager().removeAssociationListener(pfmListener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +        if (mapMatching.isPresent()) {
    +            for (Sensor<?> sensor : entity.getEntityType().getSensors()) {
    +                if (sensor instanceof AttributeSensor && mapMatching.get().apply(sensor)) {
    +                    try {
    +                        tryTransform(machine.get(), (AttributeSensor<?>)sensor);
    +                    } catch (Exception e) {
    +                        // TODO Avoid repeated logging
    +                        Exceptions.propagateIfFatal(e);
    +                        LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +                    }
    +                }
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    --- End diff --
    
    On stop all IP/hostname sensors are removed. The port forwards are also lost presumably? Is it better to null the mapped sensors as well?


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66234819
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    --- End diff --
    
    Looking at `BrooklynAccessUtils.getBrooklynAccessibleAddress` it's looking at the entity for a PFM. How would things work with a non-default PFM - does it need to be set both at the entity and the enricher?
    Are we deprecating this method in light of the "Working with multiple networks proposal? 
    
    Looking at the big picture - the port forwarding happens on the location, using the ports of the entity. Doesn't this mean that a PFM is per location (but for practical reasons entity)?
    
    I'm trying to figure out how using a non-default PFM would look like - haven't used one before.
    
    I like the idea of always using the default one - once we have a better understanding of using non-default PFMs and a concrete use case then we can extend the enricher for 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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65912505
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                // TODO What if publicTarget is still null, but will be set soon? We're not subscribed to changes in the PortForwardManager!
    +                // TODO Should we return null or sensorVal? In this method we always return sensorVal;
    +                //      but in HostAndPortTransformingEnricher we always return null!
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    --- End diff --
    
    For the case where no explicit port is defined in source url - It's quite possible that the port mapping has the same public port as the internal one. Suggest comparing and passing -1 for the port if that's the case so that the URL doesn't contain default ports.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65988048
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    +            return true;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        return (sensorVal instanceof Integer || sensorVal instanceof Long || sensorVal instanceof Short);
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                // TODO What if publicTarget is still null, but will be set soon? We're not subscribed to changes in the PortForwardManager!
    +                // TODO Should we return null or sensorVal? In this method we always return sensorVal;
    +                //      but in HostAndPortTransformingEnricher we always return null!
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    --- End diff --
    
    I'm updating this to explicitly check if `publicTarget.hasPort()`. When constructing this URI, we should therefore never do it without an explicit port.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r66329854
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    --- End diff --
    
    The value set for the new sensor will always be a host:port, when mapping a port sensor. So "redis.port" will be an int, where as "redis.endpoint.mapped.public" will be a string ip:port.
    
    I wondered about leaving it as "redis.port.mapped.public" (which would be a simpler rule to explain), but felt that including "endpoint" would make it clearer that this is not just a port value.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177
  
    I've incorporated comments; merging now.
    
    We can discuss non-default PFMs as part of the Working with multiple networks" proposal.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65974198
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    --- End diff --
    
    The enricher doesn't do anything other than publish sensors - it therefore doesn't "join" the public network. The idea behind "face" is that it's setting the sensors for its public-face (a bit like its public facade, but it's not a "facade" in gang-of-4-patterns).


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65911293
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    --- End diff --
    
    This will match most strings (as relative URIs). Suggest checking for an existing prefix as well.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r66331540
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    +            } else {
    +                return input + ".mapped." + network;
    +            }
    +        }
    +    }
    +
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected Optional<Predicate<Sensor<?>>> mapMatching;
    +    protected Function<? super String, String> sensorNameConverter;
    +    protected PortForwardManager.AssociationListener pfmListener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        checkConfig();
    +        sensors = resolveSensorsConfig();
    +        if (sensors.isEmpty()) {
    +            mapMatching = Optional.of(resolveMapMatchingConfig());
    +        } else {
    +            mapMatching = Optional.absent();
    +        }
    +        sensorNameConverter = getRequiredConfig(SENSOR_NAME_CONVERTER);
    +        
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        pfmListener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {OnPublicNetworkEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(pfmListener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {OnPublicNetworkEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +        if (mapMatching.isPresent()) {
    +            Sensor<?> wildcardSensor = null;
    +            subscriptions().subscribe(entity, wildcardSensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    if (mapMatching.get().apply(event.getSensor())) {
    +                        LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                                new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                        tryTransform((AttributeSensor<?>)event.getSensor());
    +                    }
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (pfmListener != null) {
    +                getPortForwardManager().removeAssociationListener(pfmListener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +        if (mapMatching.isPresent()) {
    +            for (Sensor<?> sensor : entity.getEntityType().getSensors()) {
    +                if (sensor instanceof AttributeSensor && mapMatching.get().apply(sensor)) {
    +                    try {
    +                        tryTransform(machine.get(), (AttributeSensor<?>)sensor);
    +                    } catch (Exception e) {
    +                        // TODO Avoid repeated logging
    +                        Exceptions.propagateIfFatal(e);
    +                        LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +                    }
    +                }
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensorNameConverter.apply(sensor.getName()));
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            URI uri = new URI(sensorVal.toString());
    +            return uri.getScheme() != null;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        if (sensorVal instanceof Integer || sensorVal instanceof Long) {
    +            return Networking.isPortValid(((Number)sensorVal).intValue());
    +        } else if (sensorVal instanceof CharSequence) {
    +            return sensorVal.toString().trim().matches("[0-9]+");
    +        } else {
    +            return false;
    +        }
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else if (sensorVal instanceof CharSequence) {
    +            return Integer.parseInt(sensorVal.toString().trim());
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            if (!publicTarget.hasPort()) {
    +                LOG.debug("network-facing enricher not transforming {} URI {}, because no port in public-target {} for {}", new Object[] {source, sensorVal, publicTarget, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    +            } catch (URISyntaxException e) {
    +                LOG.debug("Error transforming URI "+uri+", using target "+publicTarget+"; rethrowing");
    +                throw Exceptions.propagateAnnotated("Error transforming URI "+uri+", using target "+publicTarget, e);
    +            }
    +            return Maybe.of(result.toString());
    +        } else {
    +            LOG.debug("sensor mapper not transforming URI "+uri+" because no port defined");
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformHostAndPort(Entity source, MachineLocation machine, String sensorVal) {
    +        HostAndPort hostAndPort = HostAndPort.fromString(sensorVal);
    +        if (hostAndPort.hasPort()) {
    +            int port = hostAndPort.getPort();
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} host-and-port {} because defines no port", source, hostAndPort);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformPort(Entity source, MachineLocation machine, int sensorVal) {
    +        if (Networking.isPortValid(sensorVal)) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, sensorVal);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} port {} because not a valid port", source, sensorVal);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<MachineLocation> getMachine() {
    +        return Machines.findUniqueMachineLocation(entity.getLocations());
    +    }
    +    
    +    protected PortForwardManager getPortForwardManager() {
    +        PortForwardManager portForwardManager = config().get(PORT_FORWARD_MANAGER);
    +        if (portForwardManager == null) {
    +            portForwardManager = (PortForwardManager) getManagementContext().getLocationRegistry().getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC);
    +        }
    +        return portForwardManager;
    +    }
    +
    +    protected void checkConfig() {
    +        AttributeSensor<?> sensor = getConfig(SENSOR);
    +        Collection<? extends AttributeSensor<?>> sensors = getConfig(SENSORS);
    +        Maybe<Object> rawMapMatching = config().getRaw(MAP_MATCHING);
    +        String mapMatching = config().get(MAP_MATCHING);
    +        
    +        if (sensor != null && sensors != null && sensors.isEmpty()) {
    --- End diff --
    
    Good spot! Have added tests as well.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65986072
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    --- End diff --
    
    @neykov I'm not particularly concerned about its use in Clocker 1.x, if the pattern is not a natural one elsewhere.
    
    Looking at the entity's portForwardManager config would be possible, but we don't do that for other enrichers so I don't think we should do it here.
    
    Do you think I should reuse the key `BrooklynAccessUtils.PORT_FORWARDING_MANAGER` (which is named "brooklyn.portforwarding.manager")?
    
    I even wondered about not making it configurable, so the enricher would always use the default global one!
    
    Thoughts?


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r66412844
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    --- End diff --
    
    I prefer the simpler `.port` model as per the feature proposal doc.  Reasoning:
    * it *is* a port, on the address associated with the indicated network
    * simplicity and consistency --- `XXX` always becomes `XXX.mapped.NETWORK`
    * there are times when accessing in other entities that we will need the mapped host and mapped port individually, not the mapped endpoint (eg `ssh -p`)



---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66235466
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    --- End diff --
    
    Can you explain what's the logic here? Why remove the port suffix?
    `redis.httpPort` -> `redis.http.endpoint.mapped.public`
    `redis.port` -> `redis.endpoint.mapped.public`
    Isn't endpoint reserved for url-like pairs of host + port?


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65986597
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    --- End diff --
    
    Yes, it's called on rebind. I'll add a test for it though.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177
  
    Finished review. Not sure how this gets used with non-default PFMs, otherwise looks good.


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r65909121
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    --- End diff --
    
    Is the method called on rebind? If not need to re-subscribe on rebind.


---
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 #177: Adds PublicNetworkFaceEnricher

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

    https://github.com/apache/brooklyn-server/pull/177#discussion_r65986042
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/location/access/PublicNetworkFaceEnricher.java ---
    @@ -0,0 +1,373 @@
    +/*
    + * 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.core.location.access;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class PublicNetworkFaceEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(PublicNetworkFaceEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected PortForwardManager.AssociationListener listener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        sensors = resolveSensorsConfig();
    +
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        listener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {PublicNetworkFaceEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(listener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {PublicNetworkFaceEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {PublicNetworkFaceEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (listener != null) {
    +                getPortForwardManager().removeAssociationListener(listener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensor.getName()+".mapped.public");
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            new URI(sensorVal.toString());
    --- End diff --
    
    @neykov remember we aren't doing this on every sensor, only the ones we have configured.


---
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 #177: Adds PublicNetworkFaceEnricher

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

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


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66236385
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    +            } else {
    +                return input + ".mapped." + network;
    +            }
    +        }
    +    }
    +
    +    protected Collection<AttributeSensor<?>> sensors;
    +    protected Optional<Predicate<Sensor<?>>> mapMatching;
    +    protected Function<? super String, String> sensorNameConverter;
    +    protected PortForwardManager.AssociationListener pfmListener;
    +    
    +    @Override
    +    public void setEntity(final EntityLocal entity) {
    +        super.setEntity(entity);
    +        
    +        checkConfig();
    +        sensors = resolveSensorsConfig();
    +        if (sensors.isEmpty()) {
    +            mapMatching = Optional.of(resolveMapMatchingConfig());
    +        } else {
    +            mapMatching = Optional.absent();
    +        }
    +        sensorNameConverter = getRequiredConfig(SENSOR_NAME_CONVERTER);
    +        
    +        /*
    +         * To find the transformed sensor value we need several things to be set. Therefore 
    +         * subscribe to all of them, and re-compute whenever any of the change. These are:
    +         *  - A port-mapping to exist for the relevant machine + private port.
    +         *  - The entity to have a machine location (so we can lookup the mapped port association).
    +         *  - The relevant sensors to have a value, which includes the private port.
    +         */
    +        pfmListener = new PortForwardManager.AssociationListener() {
    +            @Override
    +            public void onAssociationCreated(PortForwardManager.AssociationMetadata metadata) {
    +                Maybe<MachineLocation> machine = getMachine();
    +                if (!(machine.isPresent() && machine.get().equals(metadata.getLocation()))) {
    +                    // not related to this entity's machine; ignoring
    +                    return;
    +                }
    +                
    +                LOG.debug("{} attempting transformations, triggered by port-association {}, with machine {} of entity {}", 
    +                        new Object[] {OnPublicNetworkEnricher.this, metadata, machine.get(), entity});
    +                tryTransformAll();
    +            }
    +            @Override
    +            public void onAssociationDeleted(PortForwardManager.AssociationMetadata metadata) {
    +                // no-op
    +            }
    +        };
    +        getPortForwardManager().addAssociationListener(pfmListener, Predicates.alwaysTrue());
    +        
    +        subscriptions().subscribe(entity, AbstractEntity.LOCATION_ADDED, new SensorEventListener<Location>() {
    +            @Override public void onEvent(SensorEvent<Location> event) {
    +                LOG.debug("{} attempting transformations, triggered by location-added {}, to {}", new Object[] {OnPublicNetworkEnricher.this, event.getValue(), entity});
    +                tryTransformAll();
    +            }});
    +
    +        for (AttributeSensor<?> sensor : sensors) {
    +            subscriptions().subscribe(entity, sensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                            new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                    tryTransform((AttributeSensor<?>)event.getSensor());
    +                }});
    +        }
    +        if (mapMatching.isPresent()) {
    +            Sensor<?> wildcardSensor = null;
    +            subscriptions().subscribe(entity, wildcardSensor, new SensorEventListener<Object>() {
    +                @Override public void onEvent(SensorEvent<Object> event) {
    +                    if (mapMatching.get().apply(event.getSensor())) {
    +                        LOG.debug("{} attempting transformations, triggered by sensor-event {}->{}, to {}", 
    +                                new Object[] {OnPublicNetworkEnricher.this, event.getSensor().getName(), event.getValue(), entity});
    +                        tryTransform((AttributeSensor<?>)event.getSensor());
    +                    }
    +                }});
    +        }
    +
    +        tryTransformAll();
    +    }
    +
    +    @Override
    +    public void destroy() {
    +        try {
    +            if (pfmListener != null) {
    +                getPortForwardManager().removeAssociationListener(pfmListener);
    +            }
    +        } finally {
    +            super.destroy();
    +        }
    +    }
    +
    +    protected void tryTransformAll() {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        for (AttributeSensor<?> sensor : sensors) {
    +            try {
    +                tryTransform(machine.get(), sensor);
    +            } catch (Exception e) {
    +                // TODO Avoid repeated logging
    +                Exceptions.propagateIfFatal(e);
    +                LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +            }
    +        }
    +        if (mapMatching.isPresent()) {
    +            for (Sensor<?> sensor : entity.getEntityType().getSensors()) {
    +                if (sensor instanceof AttributeSensor && mapMatching.get().apply(sensor)) {
    +                    try {
    +                        tryTransform(machine.get(), (AttributeSensor<?>)sensor);
    +                    } catch (Exception e) {
    +                        // TODO Avoid repeated logging
    +                        Exceptions.propagateIfFatal(e);
    +                        LOG.warn("Problem transforming sensor "+sensor+" of "+entity, e);
    +                    }
    +                }
    +            }
    +        }
    +    }
    +
    +    protected void tryTransform(AttributeSensor<?> sensor) {
    +        if (!isRunning()) {
    +            return;
    +        }
    +        Maybe<MachineLocation> machine = getMachine();
    +        if (machine.isAbsent()) {
    +            return;
    +        }
    +        tryTransform(machine.get(), sensor);
    +    }
    +    
    +    protected void tryTransform(MachineLocation machine, AttributeSensor<?> sensor) {
    +        Object sensorVal = entity.sensors().get(sensor);
    +        if (sensorVal == null) {
    +            return;
    +        }
    +        Maybe<String> newVal = transformVal(machine, sensor, sensorVal);
    +        if (newVal.isAbsent()) {
    +            return;
    +        }
    +        AttributeSensor<String> mappedSensor = Sensors.newStringSensor(sensorNameConverter.apply(sensor.getName()));
    +        if (newVal.get().equals(entity.sensors().get(mappedSensor))) {
    +            // ignore duplicate
    +            return;
    +        }
    +        LOG.debug("{} publishing value {} for transformed sensor {}, of entity {}", 
    +                new Object[] {this, newVal.get(), sensor, entity});
    +        entity.sensors().set(mappedSensor, newVal.get());
    +    }
    +    
    +    protected Maybe<String> transformVal(MachineLocation machine, AttributeSensor<?> sensor, Object sensorVal) {
    +        if (sensorVal == null) {
    +            return Maybe.absent();
    +        } else if (isPort(sensorVal)) {
    +            int port = toInteger(sensorVal);
    +            return transformPort(entity, machine, port);
    +        } else if (isUri(sensorVal)) {
    +            return transformUri(entity, machine, sensorVal.toString());
    +        } else if (isHostAndPort(sensorVal)) {
    +            return transformHostAndPort(entity, machine, sensorVal.toString());
    +        } else {
    +            // no-op; unrecognised type
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected boolean isUri(Object sensorVal) {
    +        if (sensorVal instanceof URI || sensorVal instanceof URL) {
    +            return true;
    +        }
    +        try {
    +            URI uri = new URI(sensorVal.toString());
    +            return uri.getScheme() != null;
    +        } catch (URISyntaxException e) {
    +            return false;
    +        }
    +    }
    +
    +    protected boolean isPort(Object sensorVal) {
    +        if (sensorVal instanceof Integer || sensorVal instanceof Long) {
    +            return Networking.isPortValid(((Number)sensorVal).intValue());
    +        } else if (sensorVal instanceof CharSequence) {
    +            return sensorVal.toString().trim().matches("[0-9]+");
    +        } else {
    +            return false;
    +        }
    +    }
    +
    +    protected int toInteger(Object sensorVal) {
    +        if (sensorVal instanceof Number) {
    +            return ((Number)sensorVal).intValue();
    +        } else if (sensorVal instanceof CharSequence) {
    +            return Integer.parseInt(sensorVal.toString().trim());
    +        } else {
    +            throw new IllegalArgumentException("Expected number but got "+sensorVal+" of type "+(sensorVal != null ? sensorVal.getClass() : null));
    +        }
    +    }
    +
    +    protected boolean isHostAndPort(Object sensorVal) {
    +        if (sensorVal instanceof HostAndPort) {
    +            return true;
    +        } else if (sensorVal instanceof String) {
    +            try {
    +                HostAndPort hostAndPort = HostAndPort.fromString((String)sensorVal);
    +                return hostAndPort.hasPort();
    +            } catch (IllegalArgumentException e) {
    +                return false;
    +            }
    +        }
    +        return false;
    +    }
    +
    +    protected Maybe<String> transformUri(Entity source, MachineLocation machine, String sensorVal) {
    +        URI uri = URI.create(sensorVal);
    +        int port = uri.getPort();
    +        if (port == -1 && "http".equalsIgnoreCase(uri.getScheme())) port = 80;
    +        if (port == -1 && "https".equalsIgnoreCase(uri.getScheme())) port = 443;
    +
    +        if (port != -1) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.trace("network-facing enricher not transforming {} URI {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            if (!publicTarget.hasPort()) {
    +                LOG.debug("network-facing enricher not transforming {} URI {}, because no port in public-target {} for {}", new Object[] {source, sensorVal, publicTarget, machine});
    +                return Maybe.absent();
    +            }
    +            URI result;
    +            try {
    +                result = new URI(uri.getScheme(), uri.getUserInfo(), publicTarget.getHostText(), publicTarget.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    +            } catch (URISyntaxException e) {
    +                LOG.debug("Error transforming URI "+uri+", using target "+publicTarget+"; rethrowing");
    +                throw Exceptions.propagateAnnotated("Error transforming URI "+uri+", using target "+publicTarget, e);
    +            }
    +            return Maybe.of(result.toString());
    +        } else {
    +            LOG.debug("sensor mapper not transforming URI "+uri+" because no port defined");
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformHostAndPort(Entity source, MachineLocation machine, String sensorVal) {
    +        HostAndPort hostAndPort = HostAndPort.fromString(sensorVal);
    +        if (hostAndPort.hasPort()) {
    +            int port = hostAndPort.getPort();
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, port);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} host-and-port {} because defines no port", source, hostAndPort);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<String> transformPort(Entity source, MachineLocation machine, int sensorVal) {
    +        if (Networking.isPortValid(sensorVal)) {
    +            HostAndPort publicTarget = getPortForwardManager().lookup(machine, sensorVal);
    +            if (publicTarget == null) {
    +                LOG.debug("network-facing enricher not transforming {} host-and-port {}, because no port-mapping for {}", new Object[] {source, sensorVal, machine});
    +                return Maybe.absent();
    +            }
    +            return Maybe.of(publicTarget.toString());
    +        } else {
    +            LOG.debug("network-facing enricher not transforming {} port {} because not a valid port", source, sensorVal);
    +            return Maybe.absent();
    +        }
    +    }
    +
    +    protected Maybe<MachineLocation> getMachine() {
    +        return Machines.findUniqueMachineLocation(entity.getLocations());
    +    }
    +    
    +    protected PortForwardManager getPortForwardManager() {
    +        PortForwardManager portForwardManager = config().get(PORT_FORWARD_MANAGER);
    +        if (portForwardManager == null) {
    +            portForwardManager = (PortForwardManager) getManagementContext().getLocationRegistry().getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC);
    +        }
    +        return portForwardManager;
    +    }
    +
    +    protected void checkConfig() {
    +        AttributeSensor<?> sensor = getConfig(SENSOR);
    +        Collection<? extends AttributeSensor<?>> sensors = getConfig(SENSORS);
    +        Maybe<Object> rawMapMatching = config().getRaw(MAP_MATCHING);
    +        String mapMatching = config().get(MAP_MATCHING);
    +        
    +        if (sensor != null && sensors != null && sensors.isEmpty()) {
    --- End diff --
    
    Should be `!sensors.isEmpty()`


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66411519
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    + *          brooklyn.config:
    + *            sensor: main.uri
    + * - type: org.apache.brooklyn.entity.proxy.nginx.NginxController
    + *   brooklyn.config:
    + *     member.sensor.hostandport: $brooklyn:sensor("main.uri.mapped.public")
    + *     serverPool: cluster
    + * }
    + * </pre>
    + */
    +@Beta
    +public class OnPublicNetworkEnricher extends AbstractEnricher {
    +
    +    // TODO Is this the best package for the enricher?
    +    //
    +    // TODO Need more logging, particularly for when the value has *not* been transformed.
    +    //
    +    // TODO What if the sensor has an unrelated hostname - we will currently still transform this!
    +    // That seems acceptable: if the user configures it to look at the sensor, then we can make
    +    // assumptions that the sensor's value will need translated.
    +    //
    +    // TODO If there is no port-mapping, should we advertise the original sensor value?
    +    // That would allow the enricher to be used for an entity in a private network, and for
    +    // it to be a no-op in a public cloud (so the same blueprint can be used in both). 
    +    // However I don't think we should publish the original value: it could be the association
    +    // just hasn't been created yet. If we publish the wrong (i.e. untransformed) value, that
    +    // will cause other entity's using attributeWhenReady to immediately trigger.
    +
    +    private static final Logger LOG = LoggerFactory.getLogger(OnPublicNetworkEnricher.class);
    +
    +    @SuppressWarnings("serial")
    +    public static final ConfigKey<AttributeSensor<?>> SENSOR = ConfigKeys.newConfigKey(
    +            new TypeToken<AttributeSensor<?>>() {}, 
    +            "sensor",
    +            "The sensor whose mapped value is to be re-published (with suffix \"mapped.public\"); "
    +                    + "either 'sensor' or 'sensors' should be specified");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Collection<? extends AttributeSensor<?>>> SENSORS = ConfigKeys.newConfigKey(
    +            new TypeToken<Collection<? extends AttributeSensor<?>>>() {}, 
    +            "sensors",
    +            "The multiple sensors whose mapped values are to be re-published (with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to 'mapAll'");
    +
    +    public static ConfigKey<String> MAP_MATCHING = ConfigKeys.newStringConfigKey(
    +            "mapMatching",
    +            "Whether to map all, based on a sensor naming convention (re-published with suffix \"mapped.public\"); "
    +                    + "if neither 'sensor' or 'sensors' is specified, defaults to matchin case-insensitive suffix of "
    +                    + "'port', 'uri', 'url' or 'endpoint' ",
    +            "(?i).*(port|uri|url|endpoint)");
    +
    +    @SuppressWarnings("serial")
    +    public static ConfigKey<Function<? super String, String>> SENSOR_NAME_CONVERTER = ConfigKeys.newConfigKey(
    +            new TypeToken<Function<? super String, String>>() {},
    +            "sensorNameConverter",
    +            "The converter to use, to map from the original sensor name to the re-published sensor name",
    +            new SensorNameConverter("public"));
    +
    +    public static final ConfigKey<PortForwardManager> PORT_FORWARD_MANAGER = ConfigKeys.newConfigKey(
    +            PortForwardManager.class, 
    +            "portForwardManager",
    +            "The PortForwardManager storing the port-mappings; if null, the global instance will be used");
    +    
    +    public static class SensorNameConverter implements Function<String, String> {
    +        private final String network;
    +        
    +        public SensorNameConverter(String network) {
    +            this.network = network;
    +        }
    +        
    +        @Override
    +        public String apply(String input) {
    +            if (input == null) throw new NullPointerException("Sensor name must not be null");
    +            String lowerInput = input.toLowerCase();
    +            if (lowerInput.endsWith("uri")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("url")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("endpoint")) {
    +                return input + ".mapped." + network;
    +            } else if (lowerInput.endsWith("port")) {
    +                String prefix = input.substring(0, input.length() - "port".length());
    +                if (prefix.endsWith(".")) prefix = prefix.substring(0, prefix.length() - 1);
    +                return prefix + ".endpoint.mapped." + network;
    --- End diff --
    
    Makes sense with that explanation in mind. But then mapping urls seems wrong. The workflow should be port -> mapped.endpoint.public -> main.uri. Mapping urls means that one of them will always be wrong (either the source or the mapped one).


---
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 #177: Adds PublicNetworkFaceEnricher

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/177#discussion_r66230638
  
    --- Diff: core/src/main/java/org/apache/brooklyn/core/network/OnPublicNetworkEnricher.java ---
    @@ -0,0 +1,496 @@
    +/*
    + * 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.core.network;
    +
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.Collection;
    +
    +import org.apache.brooklyn.api.entity.Entity;
    +import org.apache.brooklyn.api.entity.EntityLocal;
    +import org.apache.brooklyn.api.location.Location;
    +import org.apache.brooklyn.api.location.MachineLocation;
    +import org.apache.brooklyn.api.sensor.AttributeSensor;
    +import org.apache.brooklyn.api.sensor.Sensor;
    +import org.apache.brooklyn.api.sensor.SensorEvent;
    +import org.apache.brooklyn.api.sensor.SensorEventListener;
    +import org.apache.brooklyn.config.ConfigKey;
    +import org.apache.brooklyn.core.config.ConfigKeys;
    +import org.apache.brooklyn.core.enricher.AbstractEnricher;
    +import org.apache.brooklyn.core.entity.AbstractEntity;
    +import org.apache.brooklyn.core.location.Machines;
    +import org.apache.brooklyn.core.location.access.PortForwardManager;
    +import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationListener;
    +import org.apache.brooklyn.core.location.access.PortForwardManager.AssociationMetadata;
    +import org.apache.brooklyn.core.sensor.Sensors;
    +import org.apache.brooklyn.util.core.flags.TypeCoercions;
    +import org.apache.brooklyn.util.exceptions.Exceptions;
    +import org.apache.brooklyn.util.guava.Maybe;
    +import org.apache.brooklyn.util.net.Networking;
    +import org.apache.brooklyn.util.text.StringPredicates;
    +import org.apache.brooklyn.util.text.Strings;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import com.google.common.annotations.Beta;
    +import com.google.common.base.Function;
    +import com.google.common.base.Optional;
    +import com.google.common.base.Predicate;
    +import com.google.common.base.Predicates;
    +import com.google.common.collect.Lists;
    +import com.google.common.net.HostAndPort;
    +import com.google.common.reflect.TypeToken;
    +
    +/**
    + * Can be added to an entity so that it advertises its mapped ports (according to the port-mappings
    + * recorded in the PortForwardManager). This can be used with sensors of type URI, HostAndPort
    + * or plain integer port values. The port-mappings is retrieved by looking up the entity's machine
    + * and the private port, in the PortForwardManager's recorded port-mappings.
    + * 
    + * For example, to configure each Tomcat node to publish its mapped uri, and to use that sensor
    + * in Nginx for the target servers:
    + * <pre>
    + * {@code
    + * services:
    + * - type: cluster
    + *   id: cluster
    + *   brooklyn.config:
    + *    memberSpec:
    + *      $brooklyn:entitySpec:
    + *        type: org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
    + *        brooklyn.enrichers:
    + *        - type: org.apache.brooklyn.core.location.access.PublicNetworkFaceEnricher
    --- End diff --
    
    Update class name in the example


---
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.
---