You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/12/15 17:57:35 UTC

[8/9] incubator-brooklyn git commit: BROOKLYN-95 - make nginx re-configurable, to switch to ssl while live

BROOKLYN-95 - make nginx re-configurable, to switch to ssl while live

a bit sloppy, running connectSensors multiple times, but it works.
better would be a restart which does install again, or to facilitate starting at the same VM.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/a952d6b7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/a952d6b7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/a952d6b7

Branch: refs/heads/master
Commit: a952d6b7258b0d695ba7a8a591d6caf6e1faaebf
Parents: 25dccf0
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Dec 2 16:28:15 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Dec 12 13:54:55 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/entity/basic/EntityPredicates.java |  2 +-
 .../event/feed/AttributePollHandler.java        | 12 +++-
 .../main/java/brooklyn/event/feed/Poller.java   |  2 -
 .../java/brooklyn/event/feed/http/HttpFeed.java |  2 +-
 .../event/feed/http/HttpPollConfig.java         | 15 +----
 .../main/java/brooklyn/util/http/HttpTool.java  | 17 ++++++
 .../entity/proxy/AbstractController.java        |  2 +-
 .../entity/proxy/AbstractControllerImpl.java    | 21 ++++---
 .../brooklyn/entity/proxy/LoadBalancer.java     |  9 ++-
 .../entity/proxy/nginx/NginxController.java     |  5 ++
 .../entity/proxy/nginx/NginxControllerImpl.java | 41 +++++++++++--
 .../entity/proxy/nginx/NginxSshDriver.java      | 29 +++++++---
 .../brooklyn/entity/proxy/nginx/UrlMapping.java |  1 -
 .../nginx/NginxHttpsSslIntegrationTest.java     | 60 ++++++++++++++++++--
 .../qa/load/SimulatedNginxControllerImpl.java   |  5 +-
 .../java/brooklyn/rest/api/EntityConfigApi.java |  3 +-
 .../main/java/brooklyn/rest/api/SensorApi.java  |  3 +-
 .../rest/resources/EntityConfigResource.java    |  8 +--
 .../brooklyn/rest/resources/SensorResource.java |  8 +--
 19 files changed, 183 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index 12b656d..5906d78 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -318,7 +318,7 @@ public class EntityPredicates {
         }
         @Override
         public boolean apply(@Nullable Entity input) {
-            return (input != null) && group.hasMember(input);
+            return (group != null) && (input != null) && group.hasMember(input);
         }
         @Override
         public String toString() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java b/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
index 19538b3..2029237 100644
--- a/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
+++ b/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
@@ -23,9 +23,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.Lifecycle.Transition;
 import brooklyn.event.AttributeSensor;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.task.Tasks;
@@ -157,7 +160,7 @@ public class AttributePollHandler<V> implements PollHandler<V> {
             // get a non-volatile value
             Long currentProblemStartTimeCache = currentProblemStartTime;
             long expiryTime = 
-                    lastSuccessTime!=null ? lastSuccessTime+logWarningGraceTime.toMilliseconds() :
+                    (lastSuccessTime!=null && !isTransitioningOrStopped()) ? lastSuccessTime+logWarningGraceTime.toMilliseconds() :
                     currentProblemStartTimeCache!=null ? currentProblemStartTimeCache+logWarningGraceTimeOnStartup.toMilliseconds() :
                     nowTime+logWarningGraceTimeOnStartup.toMilliseconds();
             if (!lastWasProblem) {
@@ -194,6 +197,13 @@ public class AttributePollHandler<V> implements PollHandler<V> {
         }
     }
 
+    protected boolean isTransitioningOrStopped() {
+        if (entity==null) return false;
+        Transition expected = entity.getAttribute(Attributes.SERVICE_STATE_EXPECTED);
+        if (expected==null) return false;
+        return (expected.getState()==Lifecycle.STARTING || expected.getState()==Lifecycle.STOPPING || expected.getState()==Lifecycle.STOPPED);
+    }
+
     @SuppressWarnings("unchecked")
     protected void setSensor(Object v) {
         if (Entities.isNoLongerManaged(entity)) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/event/feed/Poller.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/Poller.java b/core/src/main/java/brooklyn/event/feed/Poller.java
index 6fa9147..1bb87c9 100644
--- a/core/src/main/java/brooklyn/event/feed/Poller.java
+++ b/core/src/main/java/brooklyn/event/feed/Poller.java
@@ -79,8 +79,6 @@ public class Poller<V> {
                             handler.onFailure(val);
                         }
                     } catch (Exception e) {
-                        // 2013-12-21 AH adding add'l logging because seeing strange scheduled task abortion from here
-                        // even though all paths should be catching it
                         if (loggedPreviousException) {
                             if (log.isTraceEnabled()) log.trace("PollJob for {}, repeated consecutive failures, handling {} using {}", new Object[] {job, e, handler});
                         } else {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java
index 255443a..ab9dcd1 100644
--- a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java
+++ b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java
@@ -298,7 +298,7 @@ public class HttpFeed extends AbstractFeed {
                 URI uri = config.buildUri(builder.baseUri, baseUriVars);
                 baseUriProvider = Suppliers.ofInstance(uri);
             } else if (!builder.baseUriVars.isEmpty()) {
-                throw new IllegalStateException("Not permitted to supply URI vars when using a URI provider");
+                throw new IllegalStateException("Not permitted to supply URI vars when using a URI provider; pass the vars to the provider instead");
             }
             checkNotNull(baseUriProvider);
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java b/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java
index 979e629..3476373 100644
--- a/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java
+++ b/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java
@@ -27,15 +27,12 @@ import brooklyn.event.AttributeSensor;
 import brooklyn.event.feed.PollConfig;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.http.HttpTool;
 import brooklyn.util.http.HttpToolResponse;
-import brooklyn.util.net.URLParamEncoder;
 import brooklyn.util.time.Duration;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 
 public class HttpPollConfig<T> extends PollConfig<HttpToolResponse, T, HttpPollConfig<T>> {
 
@@ -130,15 +127,7 @@ public class HttpPollConfig<T> extends PollConfig<HttpToolResponse, T, HttpPollC
         Map<String,String> allvars = concat(baseUriVars, vars);
         
         if (allvars != null && allvars.size() > 0) {
-            Iterable<String> args = Iterables.transform(allvars.entrySet(), 
-                    new Function<Map.Entry<String,String>,String>() {
-                        @Override public String apply(Map.Entry<String,String> entry) {
-                            String k = entry.getKey();
-                            String v = entry.getValue();
-                            return URLParamEncoder.encode(k) + (v != null ? "=" + URLParamEncoder.encode(v) : "");
-                        }
-                    });
-            uri += "?" + Joiner.on("&").join(args);
+            uri += "?" + HttpTool.encodeUrlParams(allvars);
         }
         
         return URI.create(uri);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/core/src/main/java/brooklyn/util/http/HttpTool.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/http/HttpTool.java b/core/src/main/java/brooklyn/util/http/HttpTool.java
index a81b928..b57c157 100644
--- a/core/src/main/java/brooklyn/util/http/HttpTool.java
+++ b/core/src/main/java/brooklyn/util/http/HttpTool.java
@@ -66,9 +66,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.URLParamEncoder;
+import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
 
 public class HttpTool {
@@ -362,4 +367,16 @@ public class HttpTool {
         return "Basic "+Base64.encodeBase64String( (credentials.getUserName()+":"+credentials.getPassword()).getBytes() );
     }
 
+    public static String encodeUrlParams(Map<?,?> data) {
+        if (data==null) return "";
+        Iterable<String> args = Iterables.transform(data.entrySet(), 
+            new Function<Map.Entry<?,?>,String>() {
+            @Override public String apply(Map.Entry<?,?> entry) {
+                Object k = entry.getKey();
+                Object v = entry.getValue();
+                return URLParamEncoder.encode(Strings.toString(k)) + (v != null ? "=" + URLParamEncoder.encode(Strings.toString(v)) : "");
+            }
+        });
+        return Joiner.on("&").join(args);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractController.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractController.java b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractController.java
index fd00924..e2ba3a1 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractController.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractController.java
@@ -41,7 +41,7 @@ public interface AbstractController extends SoftwareProcess, LoadBalancer {
 
     @SetFromFlag("ssl")
     ConfigKey<ProxySslConfig> SSL_CONFIG = ConfigKeys.newConfigKey(ProxySslConfig.class,
-            "proxy.ssl.config", "configuration (e.g. certificates) for SSL; will use SSL if set, not use SSL if not set");
+            "proxy.ssl.config", "Configuration (e.g. certificates) for SSL; causes server to run with HTTPS instead of HTTP");
     
 
     @SetFromFlag("serviceUpUrlPath")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
index bf960f9..c7f518e 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java
@@ -112,7 +112,7 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
             return; // no-op
         }
         if (serverPoolMemberTrackerPolicy != null) {
-            LOG.warn("Call to addServerPoolMemberTrackingPolicy when serverPoolMemberTrackingPolicy already exists, in {}", this);
+            LOG.debug("Call to addServerPoolMemberTrackingPolicy when serverPoolMemberTrackingPolicy already exists, removing and re-adding, in {}", this);
             removeServerPoolMemberTrackingPolicy();
         }
         for (Policy p: getPolicies()) {
@@ -187,6 +187,7 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
         } 
     }
 
+    @SuppressWarnings("deprecation")
     @Override
     public void onManagementNoLongerMaster() {
         super.onManagementNoLongerMaster(); // TODO remove when deprecated method in parent removed
@@ -226,7 +227,10 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
     
     @Override
     public Integer getPort() {
-        return getAttribute(PROXY_HTTP_PORT);
+        if (isSsl())
+            return getAttribute(PROXY_HTTPS_PORT);
+        else
+            return getAttribute(PROXY_HTTP_PORT);
     }
 
     /** primary URL this controller serves, if one can / has been inferred */
@@ -254,7 +258,7 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
     public abstract void reload();
 
     protected String inferProtocol() {
-        return getConfig(SSL_CONFIG)!=null ? "https" : "http";
+        return isSsl() ? "https" : "http";
     }
     
     /** returns URL, if it can be inferred; null otherwise */
@@ -285,13 +289,17 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
     protected Collection<Integer> getRequiredOpenPorts() {
         Collection<Integer> result = super.getRequiredOpenPorts();
         if (groovyTruth(getAttribute(PROXY_HTTP_PORT))) result.add(getAttribute(PROXY_HTTP_PORT));
+        if (groovyTruth(getAttribute(PROXY_HTTPS_PORT))) result.add(getAttribute(PROXY_HTTPS_PORT));
         return result;
     }
 
     @Override
     protected void preStart() {
         super.preStart();
-
+        computePortsAndUrls();
+    }
+    
+    protected void computePortsAndUrls() {
         AttributeSensor<String> hostAndPortSensor = getConfig(HOST_AND_PORT_SENSOR);
         Maybe<Object> hostnameSensor = getConfigRaw(HOSTNAME_SENSOR, true);
         Maybe<Object> portSensor = getConfigRaw(PORT_NUMBER_SENSOR, true);
@@ -312,11 +320,6 @@ public abstract class AbstractControllerImpl extends SoftwareProcessImpl impleme
     @Override
     protected void connectSensors() {
         super.connectSensors();
-        if (getUrl()==null) {
-            setAttribute(MAIN_URI, URI.create(inferUrl()));
-            setAttribute(ROOT_URL, inferUrl());
-        }
-        
         // TODO when rebind policies, and rebind calls connectSensors, then this will cause problems.
         // Also relying on addServerPoolMemberTrackingPolicy to set the serverPoolAddresses and serverPoolTargets.
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancer.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancer.java b/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancer.java
index 0502c3f..32e2fc9 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancer.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/LoadBalancer.java
@@ -84,8 +84,13 @@ public interface LoadBalancer extends Entity, Startable {
     @SetFromFlag("port")
     /** port where this controller should live */
     public static final PortAttributeSensorAndConfigKey PROXY_HTTP_PORT = new PortAttributeSensorAndConfigKey(
-            "proxy.http.port", "Main HTTP port where this proxy listens", ImmutableList.of(8000, "8001+"));
-    
+            "proxy.http.port", "Main port where this proxy listens if using HTTP", ImmutableList.of(8000, "8001+"));
+
+    @SetFromFlag("httpsPort")
+    /** port where this controller should live */
+    public static final PortAttributeSensorAndConfigKey PROXY_HTTPS_PORT = new PortAttributeSensorAndConfigKey(
+            "proxy.https.port", "Main port where this proxy listens if using HTTPS", ImmutableList.of(8443, "8443+"));
+
     @SetFromFlag("protocol")
     public static final BasicAttributeSensorAndConfigKey<String> PROTOCOL = new BasicAttributeSensorAndConfigKey<String>(
             String.class, "proxy.protocol", "Main URL protocol this proxy answers (typically http or https)", null);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxController.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxController.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxController.java
index 88d1bb9..21b3c89 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxController.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxController.java
@@ -134,4 +134,9 @@ public interface NginxController extends AbstractController, HasShortName {
     
     public static final AttributeSensor<Boolean> NGINX_URL_ANSWERS_NICELY = Sensors.newBooleanSensor( "nginx.url.answers.nicely");
     public static final AttributeSensor<String> PID_FILE = Sensors.newStringSensor( "nginx.pid.file", "PID file");
+    
+    public interface NginxControllerInternal {
+        public void doExtraConfigurationDuringStart();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
index 74727fb..ef5e99a 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
@@ -18,9 +18,11 @@
  */
 package brooklyn.entity.proxy.nginx;
 
+import java.net.URI;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.slf4j.Logger;
@@ -36,21 +38,25 @@ import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.proxy.AbstractControllerImpl;
 import brooklyn.entity.proxy.ProxySslConfig;
+import brooklyn.entity.proxy.nginx.NginxController.NginxControllerInternal;
 import brooklyn.event.SensorEvent;
 import brooklyn.event.SensorEventListener;
 import brooklyn.event.feed.ConfigToAttributes;
 import brooklyn.event.feed.http.HttpFeed;
 import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.management.SubscriptionHandle;
 import brooklyn.policy.PolicySpec;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.file.ArchiveUtils;
 import brooklyn.util.guava.Functionals;
+import brooklyn.util.http.HttpTool;
 import brooklyn.util.http.HttpToolResponse;
 import brooklyn.util.stream.Streams;
 import brooklyn.util.text.Strings;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -58,13 +64,14 @@ import com.google.common.collect.Sets;
 /**
  * Implementation of the {@link NginxController} entity.
  */
-public class NginxControllerImpl extends AbstractControllerImpl implements NginxController {
+public class NginxControllerImpl extends AbstractControllerImpl implements NginxController, NginxControllerInternal {
 
     private static final Logger LOG = LoggerFactory.getLogger(NginxControllerImpl.class);
 
     private volatile HttpFeed httpFeed;
     private final Set<String> installedKeysCache = Sets.newLinkedHashSet();
     protected UrlMappingsMemberTrackerPolicy urlMappingsMemberTrackerPolicy;
+    protected SubscriptionHandle targetAddressesHandler;
 
     @Override
     public void reload() {
@@ -82,19 +89,31 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
         return getConfig(STICKY);
     }
 
+    private class UrlInferencer implements Supplier<URI> {
+        private Map<String, String> parameters;
+        private UrlInferencer(Map<String,String> parameters) {
+            this.parameters = parameters;
+        }
+        @Override public URI get() { 
+            String baseUrl = inferUrl(true);
+            if (parameters==null || parameters.isEmpty())
+                return URI.create(baseUrl);
+            return URI.create(baseUrl+"?"+HttpTool.encodeUrlParams(parameters));
+        }
+    }
+    
     @Override
     public void connectSensors() {
         super.connectSensors();
 
         ConfigToAttributes.apply(this);
-        String accessibleRootUrl = inferUrl(true);
 
         // "up" is defined as returning a valid HTTP response from nginx (including a 404 etc)
         httpFeed = HttpFeed.builder()
+                .uniqueTag("nginx-poll")
                 .entity(this)
                 .period(getConfig(HTTP_POLL_PERIOD))
-                .baseUri(accessibleRootUrl)
-                .baseUriVars(ImmutableMap.of("include-runtime", "true"))
+                .baseUri(new UrlInferencer(ImmutableMap.of("include-runtime", "true")))
                 .poll(new HttpPollConfig<Boolean>(NGINX_URL_ANSWERS_NICELY)
                         // Any response from Nginx is good.
                         .checkSuccess(Predicates.alwaysTrue())
@@ -115,6 +134,7 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
             ServiceNotUpLogic.updateNotUpIndicator(this, NGINX_URL_ANSWERS_NICELY, "No response from nginx yet");
         }
         addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+            .uniqueTag("not-up-unless-url-answers")
             .from(NGINX_URL_ANSWERS_NICELY)
             .computing(Functionals.ifNotEquals(true).value("URL where nginx listens is not answering correctly (with expected header)") )
             .build());
@@ -122,9 +142,9 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
 
         // Can guarantee that parent/managementContext has been set
         Group urlMappings = getConfig(URL_MAPPINGS);
-        if (urlMappings != null) {
+        if (urlMappings!=null && urlMappingsMemberTrackerPolicy==null) {
             // Listen to the targets of each url-mapping changing
-            subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() {
+            targetAddressesHandler = subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() {
                     @Override public void onEvent(SensorEvent<Collection<String>> event) {
                         updateNeeded();
                     }
@@ -139,6 +159,12 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
     protected void removeUrlMappingsMemberTrackerPolicy() {
         if (urlMappingsMemberTrackerPolicy != null) {
             removePolicy(urlMappingsMemberTrackerPolicy);
+            urlMappingsMemberTrackerPolicy = null;
+        }
+        Group urlMappings = getConfig(URL_MAPPINGS);
+        if (urlMappings!=null && targetAddressesHandler!=null) {
+            unsubscribe(urlMappings, targetAddressesHandler);
+            targetAddressesHandler = null;
         }
     }
     
@@ -181,7 +207,10 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
     }
 
     public void doExtraConfigurationDuringStart() {
+        computePortsAndUrls();
         reconfigureService();
+        // reconnect sensors if ports have changed
+        connectSensors();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
index 91c69e5..1c73374 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java
@@ -33,6 +33,7 @@ import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.lifecycle.ScriptHelper;
 import brooklyn.entity.drivers.downloads.DownloadResolver;
+import brooklyn.entity.proxy.AbstractController;
 import brooklyn.location.OsDetails;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.management.ManagementContext;
@@ -99,10 +100,15 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
         return format("%s/%s", getRunDir(), NGINX_PID_FILE);
     }
 
+    @Deprecated /** @deprecated since 0.7.0 use #getPort */
     public Integer getHttpPort() {
         return getEntity().getPort();
     }
 
+    public Integer getPort() {
+        return getEntity().getPort();
+    }
+
     @Override
     public void rebind() {
         customizationCompleted = true;
@@ -111,7 +117,13 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
     @Override
     public void postLaunch() {
         entity.setAttribute(NginxController.PID_FILE, getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME);
-        entity.setAttribute(Attributes.HTTP_PORT, getHttpPort());
+        if (((AbstractController)entity).isSsl()) {
+            entity.setAttribute(Attributes.HTTPS_PORT, getPort());
+            ((EntityInternal)entity).removeAttribute(Attributes.HTTP_PORT);
+        } else {
+            entity.setAttribute(Attributes.HTTP_PORT, getPort());
+            ((EntityInternal)entity).removeAttribute(Attributes.HTTPS_PORT);
+        }
         super.postLaunch();
     }
 
@@ -288,7 +300,6 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
         }
 
         customizationCompleted = true;
-        getEntity().doExtraConfigurationDuringStart();
     }
 
     @Override
@@ -301,7 +312,9 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
         // TODO if can't be root, and ports > 1024 are in the allowed port range,
         // prefer that; could do this on SshMachineLocation which implements PortSupplier,
         // invoked from PortAttrSensorAndConfigKey, which is invoked from MachineLifecycleTasks.preStartCustom
-        Networking.checkPortsValid(MutableMap.of("httpPort", getHttpPort()));
+        Networking.checkPortsValid(MutableMap.of("port", getPort()));
+
+        getEntity().doExtraConfigurationDuringStart();
 
         // We wait for evidence of running because, using
         // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool,
@@ -311,7 +324,7 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
                 .body.append(
                         format("cd %s", getRunDir()),
                         BashCommands.requireExecutable("./sbin/nginx"),
-                        sudoBashCIfPrivilegedPort(getHttpPort(), format(
+                        sudoBashCIfPrivilegedPort(getPort(), format(
                                 "nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())),
                         format("for i in {1..10}\n" +
                                 "do\n" +
@@ -345,7 +358,7 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
                         format("cd %s", getRunDir()),
                         format("export PID=`cat %s`", getPidFile()),
                         "test -n \"$PID\" || exit 0",
-                        sudoIfPrivilegedPort(getHttpPort(), "kill $PID"))
+                        sudoIfPrivilegedPort(getPort(), "kill $PID"))
                 .execute();
     }
 
@@ -386,8 +399,8 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
         // calling waitForEntityStart()), we can guarantee that the start-thread's call to update will happen after
         // this call to reload. So we this can be a no-op, and just rely on that subsequent call to update.
 
-        if (!isRunning()) {
-            Lifecycle lifecycle = entity.getAttribute(NginxController.SERVICE_STATE_ACTUAL);
+        Lifecycle lifecycle = entity.getAttribute(NginxController.SERVICE_STATE_ACTUAL);
+        if (lifecycle==Lifecycle.STOPPING || lifecycle==Lifecycle.STOPPED || !isRunning()) {
             log.debug("Ignoring reload of nginx "+entity+", because service is not running (state "+lifecycle+")");
             return;
         }
@@ -412,7 +425,7 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements
                 .body.append(
                         format("cd %s", getRunDir()),
                         format("export PID=`cat %s`", getPidFile()),
-                        sudoIfPrivilegedPort(getHttpPort(), "kill -HUP $PID"))
+                        sudoIfPrivilegedPort(getPort(), "kill -HUP $PID"))
                 .execute();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java
index 7cd7d87..d8a0377 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java
@@ -29,7 +29,6 @@ import brooklyn.entity.basic.MethodEffector;
 import brooklyn.entity.proxy.AbstractController;
 import brooklyn.entity.proxy.ProxySslConfig;
 import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.entity.webapp.WebAppService;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
 import brooklyn.util.flags.SetFromFlag;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
index 8ba88b7..a8004d7 100644
--- a/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
@@ -32,24 +32,31 @@ import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppLiveTestSupport;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxy.LoadBalancer;
 import brooklyn.entity.proxy.ProxySslConfig;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.webapp.JavaWebAppService;
 import brooklyn.entity.webapp.WebAppService;
 import brooklyn.entity.webapp.jboss.JBoss7Server;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
 import brooklyn.location.Location;
+import brooklyn.location.basic.PortRanges;
 import brooklyn.test.Asserts;
 import brooklyn.test.HttpTestUtils;
+import brooklyn.util.exceptions.Exceptions;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 /**
  * Test the operation of the {@link NginxController} class.
  */
 public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
-    @SuppressWarnings("unused")
+    
     private static final Logger log = LoggerFactory.getLogger(NginxHttpsSslIntegrationTest.class);
 
     private NginxController nginx;
@@ -65,6 +72,13 @@ public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
         super.setUp();
         localLoc = mgmt.getLocationRegistry().resolve("localhost");
     }
+    
+    private static void urlContainsPort(NginxController nginx, PortAttributeSensorAndConfigKey sensor, String portRange) {
+        Integer port = nginx.getAttribute(sensor);
+        Assert.assertTrue(Iterables.contains(PortRanges.fromString(portRange), port), "Port "+port+" not in range "+portRange);
+        String url = Preconditions.checkNotNull(nginx.getAttribute(LoadBalancer.MAIN_URI), "main uri").toString();
+        Assert.assertTrue(url.contains(":"+port), "URL does not contain expected port; port "+port+", url "+url);
+    }
 
     public String getTestWar() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
@@ -90,12 +104,15 @@ public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
                 .configure("sticky", false)
                 .configure("serverPool", cluster)
                 .configure("domain", "localhost")
-                .configure("port", "8443+")
+                .configure("httpsPort", "8453+")
                 .configure("ssl", ssl));
         
         app.start(ImmutableList.of(localLoc));
 
+        urlContainsPort(nginx, LoadBalancer.PROXY_HTTPS_PORT, "8453+");
+
         final String url = nginx.getAttribute(WebAppService.ROOT_URL);
+        log.info("URL for nginx is "+url);
         if (!url.startsWith("https://")) Assert.fail("URL should be https: "+url);
         
         Asserts.succeedsEventually(new Runnable() {
@@ -128,8 +145,8 @@ public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
     }
 
     private String getFile(String file) {
-           return new File(getClass().getResource("/" + file).getFile()).getAbsolutePath();
-       }
+        return new File(getClass().getResource("/" + file).getFile()).getAbsolutePath();
+    }
 
     @Test(groups = "Integration")
     public void testStartsWithGlobalSsl_withPreinstalledCertificateAndKey() {
@@ -183,4 +200,39 @@ public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
             assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
         }
     }
+
+    @Test(groups = "Integration")
+    public void testStartsNonSslThenBecomesSsl() {
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+            .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+            .configure("initialSize", 1)
+            .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+        
+        nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+            .configure("serverPool", cluster)
+            .configure("domain", "localhost"));
+
+        app.start(ImmutableList.of(localLoc));
+
+        urlContainsPort(nginx, LoadBalancer.PROXY_HTTP_PORT, "8000-8100");
+        
+        ProxySslConfig ssl = ProxySslConfig.builder()
+                .certificateDestination(getFile("ssl/certs/localhost/server.crt"))
+                .keyDestination(getFile("ssl/certs/localhost/server.key"))
+                .build();
+        ((EntityInternal)nginx).setConfig(LoadBalancer.PROXY_HTTPS_PORT, PortRanges.fromString("8443+"));
+        ((EntityInternal)nginx).setConfig(NginxController.SSL_CONFIG, ssl);
+
+        try {
+            log.info("restarting nginx as ssl");
+            nginx.restart();
+            urlContainsPort(nginx, LoadBalancer.PROXY_HTTPS_PORT, "8443-8543");
+
+            app.stop();
+            
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
index 1fc6488..e5c40c2 100644
--- a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
+++ b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
@@ -99,7 +99,6 @@ public class SimulatedNginxControllerImpl extends NginxControllerImpl {
                 .period(getConfig(HTTP_POLL_PERIOD))
                 .poll(new FunctionPollConfig<Boolean,Boolean>(SERVICE_UP)
                         .callable(new Callable<Boolean>() {
-                            private int counter = 0;
                             public Boolean call() {
                                 return true;
                             }}))
@@ -158,7 +157,7 @@ public class SimulatedNginxControllerImpl extends NginxControllerImpl {
                 return;
             }
             
-            Networking.checkPortsValid(MutableMap.of("httpPort", getHttpPort()));
+            Networking.checkPortsValid(MutableMap.of("httpPort", getPort()));
 
             if (entity.getConfig(SKIP_SSH_ON_START)) {
                 // minimal ssh, so that isRunning will subsequently work
@@ -172,7 +171,7 @@ public class SimulatedNginxControllerImpl extends NginxControllerImpl {
                         .body.append(
                                 format("cd %s", getRunDir()),
                                 "echo skipping exec of requireExecutable ./sbin/nginx",
-                                sudoBashCIfPrivilegedPort(getHttpPort(), format(
+                                sudoBashCIfPrivilegedPort(getPort(), format(
                                         "echo skipping exec of nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())),
                                 format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()),
                                 format("echo $! > "+getPidFile()),

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/usage/rest-api/src/main/java/brooklyn/rest/api/EntityConfigApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/EntityConfigApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/EntityConfigApi.java
index 7c576bf..75f39b3 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/EntityConfigApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/EntityConfigApi.java
@@ -108,6 +108,7 @@ public interface EntityConfigApi {
   @ApiErrors(value = {
       @ApiError(code = 404, reason = "Could not find application or entity")
   })
+  @SuppressWarnings("rawtypes")
   public void setFromMap(
       @ApiParam(value = "Application ID or name", required = true)
       @PathParam("application") final String application,
@@ -116,7 +117,7 @@ public interface EntityConfigApi {
       @ApiParam(value = "Apply the config to all pre-existing descendants", required = false)
       @QueryParam("recurse") @DefaultValue("false") final Boolean recurse,
       @ApiParam(value = "Map of config key names to values", required = true)
-      Map<?,?> newValues
+      Map newValues
   ) ;
 
   @POST

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/usage/rest-api/src/main/java/brooklyn/rest/api/SensorApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/SensorApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/SensorApi.java
index 982aa52..9f05f52 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/SensorApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/SensorApi.java
@@ -113,13 +113,14 @@ public interface SensorApi {
   @ApiErrors(value = {
       @ApiError(code = 404, reason = "Could not find application or entity")
   })
+  @SuppressWarnings("rawtypes")
   public void setFromMap(
       @ApiParam(value = "Application ID or name", required = true)
       @PathParam("application") final String application,
       @ApiParam(value = "Entity ID or name", required = true)
       @PathParam("entity") final String entityToken,
       @ApiParam(value = "Map of sensor names to values", required = true)
-      Map<?,?> newValues
+      Map newValues
   ) ;
 
   @POST

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
index 66c1ef7..efcd248 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
@@ -110,7 +110,7 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
 
   @SuppressWarnings({ "rawtypes", "unchecked" })
   @Override
-  public void setFromMap(String application, String entityToken, Boolean recurse, Map<?, ?> newValues) {
+  public void setFromMap(String application, String entityToken, Boolean recurse, Map newValues) {
       final EntityLocal entity = brooklyn().getEntity(application, entityToken);
       if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
           throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify entity '%s'",
@@ -119,9 +119,9 @@ public class EntityConfigResource extends AbstractBrooklynRestResource implement
 
       if (LOG.isDebugEnabled())
           LOG.debug("REST user "+Entitlements.getEntitlementContext()+" setting configs "+newValues);
-      for (Map.Entry<?,?> entry: newValues.entrySet()) {
-          String configName = Strings.toString(entry.getKey());
-          Object newValue = entry.getValue();
+      for (Object entry: newValues.entrySet()) {
+          String configName = Strings.toString(((Map.Entry)entry).getKey());
+          Object newValue = ((Map.Entry)entry).getValue();
           
           ConfigKey ck = findConfig(entity, configName);
           ((EntityInternal) entity).setConfig(ck, TypeCoercions.coerce(newValue, ck.getTypeToken()));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a952d6b7/usage/rest-server/src/main/java/brooklyn/rest/resources/SensorResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/SensorResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/SensorResource.java
index 987a995..8c6f3f3 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/SensorResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/SensorResource.java
@@ -107,7 +107,7 @@ public class SensorResource extends AbstractBrooklynRestResource implements Sens
     
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
-    public void setFromMap(String application, String entityToken, Map<?, ?> newValues) {
+    public void setFromMap(String application, String entityToken, Map newValues) {
         final EntityLocal entity = brooklyn().getEntity(application, entityToken);
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
             throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify entity '%s'",
@@ -116,9 +116,9 @@ public class SensorResource extends AbstractBrooklynRestResource implements Sens
 
         if (log.isDebugEnabled())
             log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensors "+newValues);
-        for (Map.Entry<?,?> entry: newValues.entrySet()) {
-            String sensorName = Strings.toString(entry.getKey());
-            Object newValue = entry.getValue();
+        for (Object entry: newValues.entrySet()) {
+            String sensorName = Strings.toString(((Map.Entry)entry).getKey());
+            Object newValue = ((Map.Entry)entry).getValue();
             
             AttributeSensor sensor = findSensor(entity, sensorName);
             entity.setAttribute(sensor, newValue);