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 2015/12/23 12:06:44 UTC
[21/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723'
into reorg2
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
index 0000000,729351a..ca2caf7
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
@@@ -1,0 -1,372 +1,392 @@@
+ /*
+ * 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.entity.dns;
+
+ import static com.google.common.base.Preconditions.checkNotNull;
+
+ import java.net.InetAddress;
+ import java.net.MalformedURLException;
+ import java.net.URI;
+ import java.net.URL;
+ import java.net.UnknownHostException;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.HashSet;
+ import java.util.LinkedHashMap;
+ import java.util.LinkedHashSet;
+ import java.util.Map;
+ import java.util.Set;
+
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.Group;
+ import org.apache.brooklyn.api.policy.PolicySpec;
++import org.apache.brooklyn.api.sensor.Sensor;
+ import org.apache.brooklyn.core.entity.AbstractEntity;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+ import org.apache.brooklyn.entity.group.DynamicGroup;
+ import org.apache.brooklyn.entity.webapp.WebAppService;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.brooklyn.util.time.Time;
+
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Maps;
+
+ public abstract class AbstractGeoDnsServiceImpl extends AbstractEntity implements AbstractGeoDnsService {
+ private static final Logger log = LoggerFactory.getLogger(AbstractGeoDnsService.class);
+
+ @SetFromFlag
+ protected Group targetEntityProvider;
+ protected AbstractMembershipTrackingPolicy tracker;
-
++
+ protected Map<Entity, HostGeoInfo> targetHosts = Collections.synchronizedMap(new LinkedHashMap<Entity, HostGeoInfo>());
-
++
+ // We complain (at debug) when we encounter a target entity for whom we can't derive hostname/ip information;
+ // this is the commonest case for the transient condition between the time the entity is created and the time
+ // it is started (at which point the location is specified). This set contains those entities we've complained
+ // about already, to avoid repetitive logging.
+ transient protected Set<Entity> entitiesWithoutHostname = new HashSet<Entity>();
+
+ // We complain (at info/warn) when we encounter a target entity for whom we can't derive geo information, even
+ // when hostname/ip is known. This set contains those entities we've complained about already, to avoid repetitive
+ // logging.
+ transient protected Set<Entity> entitiesWithoutGeoInfo = new HashSet<Entity>();
+
+ public AbstractGeoDnsServiceImpl() {
+ super();
+ }
-
++
++ @Override
++ public void init() {
++ super.init();
++ Group initialProvider = config().get(ENTITY_PROVIDER);
++ if (initialProvider != null) {
++ setTargetEntityProvider(initialProvider);
++ }
++ }
++
+ @Override
+ public Map<Entity, HostGeoInfo> getTargetHosts() {
+ return targetHosts;
+ }
-
++
+ @Override
+ public void onManagementBecomingMaster() {
+ super.onManagementBecomingMaster();
+ startTracker();
+ }
++
+ @Override
+ public void onManagementNoLongerMaster() {
+ endTracker();
+ super.onManagementNoLongerMaster();
+ }
+
+ @Override
+ public void destroy() {
+ setServiceState(Lifecycle.DESTROYED);
+ super.destroy();
+ }
-
++
+ @Override
+ public void setServiceState(Lifecycle state) {
+ sensors().set(HOSTNAME, getHostname());
+ ServiceStateLogic.setExpectedState(this, state);
+ if (state==Lifecycle.RUNNING)
+ ServiceNotUpLogic.clearNotUpIndicator(this, SERVICE_STATE_ACTUAL);
+ else
+ ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_STATE_ACTUAL, "Not in RUNNING state");
+ }
-
++
+ @Override
+ public void setTargetEntityProvider(final Group entityProvider) {
+ this.targetEntityProvider = checkNotNull(entityProvider, "targetEntityProvider");
+ startTracker();
+ }
-
++
+ /** should set up so these hosts are targeted, and setServiceState appropriately */
+ protected abstract void reconfigureService(Collection<HostGeoInfo> targetHosts);
-
++
+ protected synchronized void startTracker() {
+ if (targetEntityProvider==null || !getManagementSupport().isDeployed()) {
+ log.debug("Tracker for "+this+" not yet active: "+targetEntityProvider+" / "+getManagementContext());
+ return;
+ }
+ endTracker();
++
++ ImmutableSet.Builder<Sensor<?>> sensorsToTrack = ImmutableSet.<Sensor<?>>builder().add(
++ HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL);
++ // Don't subscribe to lifecycle events if entities will be included regardless of their status.
++ if (Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING))) {
++ sensorsToTrack.add(Attributes.SERVICE_STATE_ACTUAL);
++ }
+ log.debug("Initializing tracker for "+this+", following "+targetEntityProvider);
+ tracker = policies().add(PolicySpec.create(MemberTrackingPolicy.class)
+ .displayName("GeoDNS targets tracker")
- .configure("sensorsToTrack", ImmutableSet.of(HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL))
- .configure("group", targetEntityProvider));
++ .configure(AbstractMembershipTrackingPolicy.SENSORS_TO_TRACK, sensorsToTrack.build())
++ .configure(AbstractMembershipTrackingPolicy.GROUP, targetEntityProvider));
+ refreshGroupMembership();
+ }
-
++
+ protected synchronized void endTracker() {
+ if (tracker == null || targetEntityProvider==null) return;
+ policies().remove(tracker);
+ tracker = null;
+ }
-
++
+ public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
+ @Override
+ protected void onEntityEvent(EventType type, Entity entity) {
+ ((AbstractGeoDnsServiceImpl)super.entity).refreshGroupMembership();
+ }
+ }
+
+ @Override
+ public abstract String getHostname();
-
++
+ long lastUpdate = -1;
-
++
+ // TODO: remove group member polling once locations can be determined via subscriptions
+ protected void refreshGroupMembership() {
+ try {
+ if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets", this);
+ if (targetEntityProvider == null)
+ return;
+ if (targetEntityProvider instanceof DynamicGroup)
+ ((DynamicGroup) targetEntityProvider).rescanEntities();
+ Set<Entity> pool = MutableSet.copyOf(targetEntityProvider instanceof Group ? ((Group)targetEntityProvider).getMembers(): targetEntityProvider.getChildren());
+ if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets, pool now {}", this, pool);
-
++
+ boolean changed = false;
++ boolean filterForRunning = Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING));
+ Set<Entity> previousOnes = MutableSet.copyOf(targetHosts.keySet());
+ for (Entity e: pool) {
- previousOnes.remove(e);
- changed |= addTargetHost(e);
++ if (!filterForRunning || Lifecycle.RUNNING.equals(e.sensors().get(Attributes.SERVICE_STATE_ACTUAL))) {
++ previousOnes.remove(e);
++ changed |= addTargetHost(e);
++ }
+ }
+ // anything left in previousOnes is no longer applicable
+ for (Entity e: previousOnes) {
- changed = true;
- removeTargetHost(e, false);
++ changed |= removeTargetHost(e, false);
+ }
-
++
+ // do a periodic full update hourly once we are active (the latter is probably not needed)
- if (changed || (lastUpdate>0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR)))
++ if (changed || (lastUpdate > 0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR))) {
+ update();
-
++ }
+ } catch (Exception e) {
+ log.error("Problem refreshing group membership: "+e, e);
+ }
+ }
-
++
+ /**
+ * Adds this host, if it is absent or if its hostname has changed.
+ * <p>
+ * For whether to use hostname or ip, see config and attributes {@link AbstractGeoDnsService#USE_HOSTNAMES},
+ * {@link Attributes#HOSTNAME} and {@link Attributes#ADDRESS} (via {@link #inferHostname(Entity)} and {@link #inferIp(Entity)}.
+ * Note that the "hostname" could in fact be an IP address, if {@link #inferHostname(Entity)} returns an IP!
+ * <p>
+ * TODO in a future release, we may change this to explicitly set the sensor(s) to look at on the entity, and
+ * be stricter about using them in order.
+ *
+ * @return true if host is added or changed
+ */
+ protected boolean addTargetHost(Entity entity) {
+ try {
+ HostGeoInfo oldGeo = targetHosts.get(entity);
+ String hostname = inferHostname(entity);
+ String ip = inferIp(entity);
+ String addr = (getConfig(USE_HOSTNAMES) || ip == null) ? hostname : ip;
-
++
+ if (addr==null) addr = ip;
+ if (addr == null) {
+ if (entitiesWithoutHostname.add(entity)) {
+ log.debug("GeoDns ignoring {} (no hostname/ip/URL info yet available)", entity);
+ }
+ return false;
+ }
-
++
+ // prefer the geo from the entity (or location parent), but fall back to inferring
+ // e.g. if it supplies a URL
+ HostGeoInfo geo = HostGeoInfo.fromEntity(entity);
+ if (geo==null) geo = inferHostGeoInfo(hostname, ip);
-
++
+ if (Networking.isPrivateSubnet(addr) && ip!=null && !Networking.isPrivateSubnet(ip)) {
+ // fix for #1216
+ log.debug("GeoDns using IP "+ip+" for "+entity+" as addr "+addr+" resolves to private subnet");
+ addr = ip;
+ }
+ if (Networking.isPrivateSubnet(addr)) {
+ if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.info("GeoDns including {}, even though {} is a private subnet (homeless entities included)", entity, addr);
+ }
+ } else {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.warn("GeoDns ignoring {} (private subnet detected for {})", entity, addr);
+ }
+ return false;
+ }
+ }
+
+ if (geo == null) {
+ if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.info("GeoDns including {}, even though no geography info available for {})", entity, addr);
+ }
+ geo = HostGeoInfo.create(addr, "unknownLocation("+addr+")", 0, 0);
+ } else {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.warn("GeoDns ignoring {} (no geography info available for {})", entity, addr);
+ }
+ return false;
+ }
+ }
+
+ if (!addr.equals(geo.getAddress())) {
+ // if the location provider did not have an address, create a new one with it
+ geo = HostGeoInfo.create(addr, geo.displayName, geo.latitude, geo.longitude);
+ }
-
++
+ // If we already knew about it, and it hasn't changed, then nothing to do
+ if (oldGeo != null && geo.getAddress().equals(oldGeo.getAddress())) {
+ return false;
+ }
-
++
+ entitiesWithoutHostname.remove(entity);
+ entitiesWithoutGeoInfo.remove(entity);
+ log.info("GeoDns adding "+entity+" at "+geo+(oldGeo != null ? " (previously "+oldGeo+")" : ""));
+ targetHosts.put(entity, geo);
+ return true;
+
+ } catch (Exception ee) {
+ log.warn("GeoDns ignoring "+entity+" (error analysing location): "+ee, ee);
+ return false;
+ }
+ }
+
+ /** remove if host removed */
+ protected boolean removeTargetHost(Entity e, boolean doUpdate) {
+ if (targetHosts.remove(e) != null) {
+ log.info("GeoDns removing reference to {}", e);
+ if (doUpdate) update();
+ return true;
+ }
+ return false;
+ }
-
++
+ protected void update() {
+ lastUpdate = System.currentTimeMillis();
-
++
+ Map<Entity, HostGeoInfo> m;
+ synchronized(targetHosts) { m = ImmutableMap.copyOf(targetHosts); }
+ if (log.isDebugEnabled()) log.debug("Full update of "+this+" ("+m.size()+" target hosts)");
-
++
+ Map<String,String> entityIdToAddress = Maps.newLinkedHashMap();
+ for (Map.Entry<Entity, HostGeoInfo> entry : m.entrySet()) {
+ entityIdToAddress.put(entry.getKey().getId(), entry.getValue().address);
+ }
-
++
+ reconfigureService(new LinkedHashSet<HostGeoInfo>(m.values()));
-
++
+ if (log.isDebugEnabled()) log.debug("Targets being set as "+entityIdToAddress);
+ sensors().set(TARGETS, entityIdToAddress);
+ }
-
++
+ protected String inferHostname(Entity entity) {
+ String hostname = entity.getAttribute(Attributes.HOSTNAME);
+ URI url = entity.getAttribute(Attributes.MAIN_URI);
+ if (url!=null) {
+ try {
+ URL u = url.toURL();
-
++
+ String hostname2 = u.getHost();
+ if (hostname==null) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" using URL {} to redirect to {} (HOSTNAME attribute is preferred, but not available)", url, entity);
+ hostname = hostname2;
+ } else if (!hostname.equals(hostname2)) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" URL {} of "+entity+" does not match advertised HOSTNAME {}; using hostname, not URL", url, hostname);
+ }
-
++
+ if (u.getPort() > 0 && u.getPort() != 80 && u.getPort() != 443) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" detected non-standard port in URL {} for {}; forwarding may not work", url, entity);
+ }
-
++
+ } catch (MalformedURLException e) {
+ log.warn("Invalid URL {} for entity {} in {}", new Object[] {url, entity, this});
+ }
+ }
+ return hostname;
+ }
-
++
+ protected String inferIp(Entity entity) {
+ return entity.getAttribute(Attributes.ADDRESS);
+ }
-
++
+ protected HostGeoInfo inferHostGeoInfo(String hostname, String ip) throws UnknownHostException {
+ HostGeoInfo geoH = null;
+ if (hostname != null) {
+ try {
+ // For some entities, the hostname can actually be an IP! Therefore use Networking.getInetAddressWithFixedName
+ InetAddress addr = Networking.getInetAddressWithFixedName(hostname);
+ geoH = HostGeoInfo.fromIpAddress(addr);
+ } catch (RuntimeException e) {
+ // Most likely caused by (a wrapped) UnknownHostException
+ Exceptions.propagateIfFatal(e);
+ if (ip == null) {
+ if (log.isTraceEnabled()) log.trace("inferHostGeoInfo failing ("+Exceptions.getFirstInteresting(e)+"): hostname="+hostname+"; ip="+ip);
+ throw e;
+ } else {
+ if (log.isTraceEnabled()) log.trace("GeoDns failed to infer GeoInfo from hostname {}; will try with IP {} ({})", new Object[] {hostname, ip, e});
+ }
+ }
+ }
+
+ // Try IP address (prior to Mar 2014 we did not do this if USE_HOSTNAME was set but don't think that is desirable due to #1216)
+ if (ip != null) {
+ if (geoH == null) {
+ InetAddress addr = Networking.getInetAddressWithFixedName(ip);
+ geoH = HostGeoInfo.fromIpAddress(addr);
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from ip {} (could not infer from hostname {})", new Object[] {geoH, ip, hostname});
+ } else {
+ geoH = HostGeoInfo.create(ip, geoH.displayName, geoH.latitude, geoH.longitude);
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}; switching it to ip {}", new Object[] {geoH, hostname, ip});
+ }
+ } else {
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}", geoH, hostname);
+ }
-
++
+ return geoH;
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
index 0000000,f421df7..58fcca4
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
@@@ -1,0 -1,70 +1,86 @@@
+ /*
+ * 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.entity.dns.geoscaling;
+
+ import java.net.URI;
+
+ import org.apache.brooklyn.api.entity.ImplementedBy;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.BasicConfigKey;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
++import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.entity.dns.AbstractGeoDnsService;
+ import org.apache.brooklyn.entity.webapp.WebAppServiceConstants;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
++/**
++ * A geo-DNS service using geoscaling.com.
++ * <p>
++ * AWS users should note that if the Brooklyn server managing this entity is in the same
++ * region as the server being geoscaled then they must set {@link #INCLUDE_HOMELESS_ENTITIES}
++ * to true, as IP lookups of the server will resolve the private address and it will be
++ * ignored by default.
++ */
+ @ImplementedBy(GeoscalingDnsServiceImpl.class)
+ public interface GeoscalingDnsService extends AbstractGeoDnsService {
+
+ @SetFromFlag("sslTrustAll")
- public static final ConfigKey<Boolean> SSL_TRUST_ALL = ConfigKeys.newBooleanConfigKey(
++ ConfigKey<Boolean> SSL_TRUST_ALL = ConfigKeys.newBooleanConfigKey(
+ "ssl.trustAll",
+ "Whether to trust all certificates, or to fail with 'peer not authenticated' if untrusted (default false)",
+ false);
++
+ @SetFromFlag("randomizeSubdomainName")
- public static final ConfigKey<Boolean> RANDOMIZE_SUBDOMAIN_NAME = new BasicConfigKey<Boolean>(
- Boolean.class, "randomize.subdomain.name");
++ ConfigKey<Boolean> RANDOMIZE_SUBDOMAIN_NAME = ConfigKeys.newBooleanConfigKey(
++ "randomize.subdomain.name");
++
+ @SetFromFlag("username")
- public static final ConfigKey<String> GEOSCALING_USERNAME = new BasicConfigKey<String>(
- String.class, "geoscaling.username");
++ ConfigKey<String> GEOSCALING_USERNAME = ConfigKeys.newStringConfigKey(
++ "geoscaling.username");
++
+ @SetFromFlag("password")
- public static final ConfigKey<String> GEOSCALING_PASSWORD = new BasicConfigKey<String>(
- String.class, "geoscaling.password");
++ ConfigKey<String> GEOSCALING_PASSWORD = ConfigKeys.newStringConfigKey(
++ "geoscaling.password");
++
+ @SetFromFlag("primaryDomainName")
- public static final ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = new BasicConfigKey<String>(
- String.class, "geoscaling.primary.domain.name");
++ ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = ConfigKeys.newStringConfigKey(
++ "geoscaling.primary.domain.name");
++
+ @SetFromFlag("smartSubdomainName")
- public static final ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = new BasicConfigKey<String>(
- String.class, "geoscaling.smart.subdomain.name");
++ ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = ConfigKeys.newStringConfigKey(
++ "geoscaling.smart.subdomain.name");
+
- public static final AttributeSensor<String> GEOSCALING_ACCOUNT = new BasicAttributeSensor<String>(
- String.class, "geoscaling.account", "Active user account for the GeoScaling.com service");
- public static final AttributeSensor<URI> MAIN_URI = Attributes.MAIN_URI;
- public static final AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
- public static final AttributeSensor<String> MANAGED_DOMAIN = new BasicAttributeSensor<String>(
- String.class, "geoscaling.managed.domain", "Fully qualified domain name that will be geo-redirected; " +
++ AttributeSensor<String> GEOSCALING_ACCOUNT = Sensors.newStringSensor(
++ "geoscaling.account", "Active user account for the GeoScaling.com service");
++
++ AttributeSensor<URI> MAIN_URI = Attributes.MAIN_URI;
++
++ AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
++
++ AttributeSensor<String> MANAGED_DOMAIN = Sensors.newStringSensor(
++ "geoscaling.managed.domain",
++ "Fully qualified domain name that will be geo-redirected; " +
+ "this will be the same as "+ROOT_URL.getName()+" but the latter will only be set when the domain has active targets");
+
- public void applyConfig();
++ void applyConfig();
+
+ /** minimum/default TTL here is 300s = 5m */
- public long getTimeToLiveSeconds();
++ long getTimeToLiveSeconds();
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
index 0000000,4273dac..e04b8ec
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
@@@ -1,0 -1,199 +1,201 @@@
+ /*
+ * 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.entity.dns.geoscaling;
+
+ import static com.google.common.base.Preconditions.checkNotNull;
+ import static org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.PROVIDE_CITY_INFO;
+
+ import java.net.URI;
+ import java.util.Collection;
+ import java.util.Set;
+
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.entity.dns.AbstractGeoDnsServiceImpl;
+ import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.Domain;
+ import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.SmartSubdomain;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.http.HttpTool;
+ import org.apache.brooklyn.util.text.Identifiers;
+ import org.apache.brooklyn.util.text.Strings;
+
+ public class GeoscalingDnsServiceImpl extends AbstractGeoDnsServiceImpl implements GeoscalingDnsService {
+
+ private static final Logger log = LoggerFactory.getLogger(GeoscalingDnsServiceImpl.class);
+
+ // Must remember any desired redirection targets if they're specified before configure() has been called.
+ private Set<HostGeoInfo> rememberedTargetHosts;
+ private GeoscalingWebClient webClient;
+
+ // These are available only after the configure() method has been invoked.
+ private boolean randomizeSmartSubdomainName;
+ private String username;
+ private String password;
+ private String primaryDomainName;
+ private String smartSubdomainName;
+
+ public GeoscalingDnsServiceImpl() {
+ }
+
+ @Override
+ public void init() {
+ super.init();
+
+ // defaulting to randomized subdomains makes deploying multiple applications easier
- if (getConfig(RANDOMIZE_SUBDOMAIN_NAME)==null) config().set(RANDOMIZE_SUBDOMAIN_NAME, true);
-
- Boolean trustAll = getConfig(SSL_TRUST_ALL);
++ if (config().get(RANDOMIZE_SUBDOMAIN_NAME) == null) {
++ config().set(RANDOMIZE_SUBDOMAIN_NAME, true);
++ }
++
++ Boolean trustAll = config().get(SSL_TRUST_ALL);
+ if (Boolean.TRUE.equals(trustAll)) {
+ webClient = new GeoscalingWebClient(HttpTool.httpClientBuilder().trustAll().build());
+ } else {
+ webClient = new GeoscalingWebClient();
+ }
+ }
+
+ // Ensure our configure() method gets called; may be able to remove this if/when the framework detects this
+ // and invokes the configure() method automatically?
+ @Override
+ public void onManagementBecomingMaster() {
+ try {
+ applyConfig();
+ } catch (Exception e) {
+ // don't prevent management coming up, but do mark it as on fire
+ log.error("Geoscaling did not come up correctly: "+e, e);
+ ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
+ }
+ super.onManagementBecomingMaster();
+ }
+
+ boolean isConfigured = false;
+
+ public synchronized void applyConfig() {
- randomizeSmartSubdomainName = getConfig(RANDOMIZE_SUBDOMAIN_NAME);
- username = getConfig(GEOSCALING_USERNAME);
- password = getConfig(GEOSCALING_PASSWORD);
- primaryDomainName = getConfig(GEOSCALING_PRIMARY_DOMAIN_NAME);
- smartSubdomainName = getConfig(GEOSCALING_SMART_SUBDOMAIN_NAME);
++ randomizeSmartSubdomainName = config().get(RANDOMIZE_SUBDOMAIN_NAME);
++ username = config().get(GEOSCALING_USERNAME);
++ password = config().get(GEOSCALING_PASSWORD);
++ primaryDomainName = config().get(GEOSCALING_PRIMARY_DOMAIN_NAME);
++ smartSubdomainName = config().get(GEOSCALING_SMART_SUBDOMAIN_NAME);
+
+ // Ensure all mandatory configuration is provided.
+ checkNotNull(username, "The GeoScaling username is not specified");
+ checkNotNull(password, "The GeoScaling password is not specified");
+ checkNotNull(primaryDomainName, "The GeoScaling primary domain name is not specified");
+
+ if (randomizeSmartSubdomainName) {
+ // if no smart subdomain specified, but random is, use something random
+ if (smartSubdomainName != null) smartSubdomainName += "-";
+ else smartSubdomainName = "";
+ smartSubdomainName += Identifiers.makeRandomId(8);
+ }
+ checkNotNull(smartSubdomainName, "The GeoScaling smart subdomain name is not specified or randomized");
+
+ String fullDomain = smartSubdomainName+"."+primaryDomainName;
+ log.info("GeoScaling service will configure redirection for '"+fullDomain+"' domain");
+ sensors().set(GEOSCALING_ACCOUNT, username);
+ sensors().set(MANAGED_DOMAIN, fullDomain);
+ sensors().set(HOSTNAME, getHostname());
+
+ isConfigured = true;
+
+ if (rememberedTargetHosts != null) {
+ reconfigureService(rememberedTargetHosts);
+ rememberedTargetHosts = null;
+ }
+ }
+
+ @Override
+ public String getHostname() {
+ String result = getAttribute(MANAGED_DOMAIN);
+ return (Strings.isBlank(result)) ? null : result;
+ }
+
+ /** minimum/default TTL here is 300s = 5m */
+ public long getTimeToLiveSeconds() { return 5*60; }
+
+ @Override
+ public void destroy() {
+ setServiceState(Lifecycle.STOPPING);
+ if (!isConfigured) return;
+
+ // Don't leave randomized subdomains configured on our GeoScaling account.
+ if (randomizeSmartSubdomainName) {
+ webClient.login(username, password);
+ Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+ SmartSubdomain smartSubdomain = (primaryDomain != null) ? primaryDomain.getSmartSubdomain(smartSubdomainName) : null;
+ if (smartSubdomain != null) {
+ log.info("Deleting randomized GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+"'");
+ smartSubdomain.delete();
+ }
+ webClient.logout();
+ }
+
+ super.destroy();
+
+ isConfigured = false;
+ }
+
+ protected void reconfigureService(Collection<HostGeoInfo> targetHosts) {
+ if (!isConfigured) {
+ this.rememberedTargetHosts = MutableSet.copyOf(targetHosts);
+ return;
+ }
+
+ webClient.login(username, password);
+ Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+ if (primaryDomain==null)
+ throw new NullPointerException(this+" got null from web client for primary domain "+primaryDomainName);
+ SmartSubdomain smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+
+ if (smartSubdomain == null) {
+ log.info("GeoScaling {} smart subdomain '{}.{}' does not exist, creating it now", new Object[] {this, smartSubdomainName, primaryDomainName});
+ // TODO use WithMutexes to ensure this is single-entrant
+ primaryDomain.createSmartSubdomain(smartSubdomainName);
+ smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+ }
+
+ if (smartSubdomain != null) {
+ log.debug("GeoScaling {} being reconfigured to use {}", this, targetHosts);
+ String script = GeoscalingScriptGenerator.generateScriptString(targetHosts);
+ smartSubdomain.configure(PROVIDE_CITY_INFO, script);
+ if (targetHosts.isEmpty()) {
+ setServiceState(Lifecycle.CREATED);
+ sensors().set(ROOT_URL, null);
+ sensors().set(MAIN_URI, null);
+ } else {
+ setServiceState(Lifecycle.RUNNING);
+ String domain = getAttribute(MANAGED_DOMAIN);
+ if (!Strings.isEmpty(domain)) {
+ sensors().set(ROOT_URL, "http://"+domain+"/");
+ sensors().set(MAIN_URI, URI.create("http://"+domain+"/"));
+ }
+ }
+ } else {
+ log.warn("Failed to retrieve or create GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+
+ "', aborting attempt to configure service");
+ setServiceState(Lifecycle.ON_FIRE);
+ }
+
+ webClient.logout();
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
index 0000000,7791418..e1e30c3
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/JavaWebAppSshDriver.java
@@@ -1,0 -1,205 +1,205 @@@
+ /*
+ * 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.entity.webapp;
+
+ import static com.google.common.base.Preconditions.checkNotNull;
+
+ import java.io.File;
+ import java.net.URI;
+ import java.util.Set;
+
+ import com.google.common.net.HostAndPort;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
+ import org.apache.brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.util.core.task.DynamicTasks;
+ import org.apache.brooklyn.util.core.task.Tasks;
+ import org.apache.brooklyn.util.core.task.ssh.SshTasks;
+ import org.apache.brooklyn.util.text.Strings;
+
+ import com.google.common.collect.ImmutableList;
+
+ public abstract class JavaWebAppSshDriver extends JavaSoftwareProcessSshDriver implements JavaWebAppDriver {
+
+ public JavaWebAppSshDriver(JavaWebAppSoftwareProcessImpl entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ public JavaWebAppSoftwareProcessImpl getEntity() {
+ return (JavaWebAppSoftwareProcessImpl) super.getEntity();
+ }
+
+ protected boolean isProtocolEnabled(String protocol) {
+ Set<String> protocols = getEnabledProtocols();
+ for (String contender : protocols) {
+ if (protocol.equalsIgnoreCase(contender)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Set<String> getEnabledProtocols() {
+ return entity.getAttribute(JavaWebAppSoftwareProcess.ENABLED_PROTOCOLS);
+ }
+
+ @Override
+ public Integer getHttpPort() {
+ return entity.getAttribute(Attributes.HTTP_PORT);
+ }
+
+ @Override
+ public Integer getHttpsPort() {
+ return entity.getAttribute(Attributes.HTTPS_PORT);
+ }
+
+ @Override
+ public HttpsSslConfig getHttpsSslConfig() {
+ return entity.getAttribute(WebAppServiceConstants.HTTPS_SSL_CONFIG);
+ }
+
+ protected String getSslKeystoreUrl() {
+ HttpsSslConfig ssl = getHttpsSslConfig();
+ return (ssl == null) ? null : ssl.getKeystoreUrl();
+ }
+
+ protected String getSslKeystorePassword() {
+ HttpsSslConfig ssl = getHttpsSslConfig();
+ return (ssl == null) ? null : ssl.getKeystorePassword();
+ }
+
+ protected String getSslKeyAlias() {
+ HttpsSslConfig ssl = getHttpsSslConfig();
+ return (ssl == null) ? null : ssl.getKeyAlias();
+ }
+
+ protected String inferRootUrl() {
+ if (isProtocolEnabled("https")) {
+ Integer port = getHttpsPort();
+ checkNotNull(port, "HTTPS_PORT sensors not set; is an acceptable port available?");
+ HostAndPort accessibleAddress = BrooklynAccessUtils.getBrooklynAccessibleAddress(getEntity(), port);
+ return String.format("https://%s:%s/", accessibleAddress.getHostText(), accessibleAddress.getPort());
+ } else if (isProtocolEnabled("http")) {
+ Integer port = getHttpPort();
+ checkNotNull(port, "HTTP_PORT sensors not set; is an acceptable port available?");
+ HostAndPort accessibleAddress = BrooklynAccessUtils.getBrooklynAccessibleAddress(getEntity(), port);
+ return String.format("http://%s:%s/", accessibleAddress.getHostText(), accessibleAddress.getPort());
+ } else {
+ throw new IllegalStateException("HTTP and HTTPS protocols not enabled for "+entity+"; enabled protocols are "+getEnabledProtocols());
+ }
+ }
+
+ @Override
+ public void postLaunch() {
+ String rootUrl = inferRootUrl();
+ entity.sensors().set(Attributes.MAIN_URI, URI.create(rootUrl));
+ entity.sensors().set(WebAppService.ROOT_URL, rootUrl);
+ }
+
+ /**
+ * if files should be placed on the server for deployment,
+ * override this to be the sub-directory of the runDir where they should be stored
+ * (or override getDeployDir() if they should be copied somewhere else,
+ * and set this null);
+ * if files are not copied to the server, but injected (e.g. JMX or uploaded)
+ * then override {@link #deploy(String, String)} as appropriate,
+ * using getContextFromDeploymentTargetName(targetName)
+ * and override this to return null
+ */
+ protected abstract String getDeploySubdir();
+
+ protected String getDeployDir() {
+ if (getDeploySubdir()==null)
+ throw new IllegalStateException("no deployment directory available for "+this);
+ return getRunDir() + "/" + getDeploySubdir();
+ }
+
+ @Override
+ public void deploy(File file) {
+ deploy(file, null);
+ }
+
+ @Override
+ public void deploy(File f, String targetName) {
+ if (targetName == null) {
+ targetName = f.getName();
+ }
+ deploy(f.toURI().toASCIIString(), targetName);
+ }
+
+ /**
+ * Deploys a URL as a webapp at the appserver.
+ *
+ * Returns a token which can be used as an argument to undeploy,
+ * typically the web context with leading slash where the app can be reached (just "/" for ROOT)
+ *
+ * @see JavaWebAppSoftwareProcess#deploy(String, String) for details of how input filenames are handled
+ */
+ @Override
+ public String deploy(final String url, final String targetName) {
+ final String canonicalTargetName = getFilenameContextMapper().convertDeploymentTargetNameToFilename(targetName);
+ final String dest = getDeployDir() + "/" + canonicalTargetName;
+ //write to a .tmp so autodeploy is not triggered during upload
+ final String tmpDest = dest + "." + Strings.makeRandomId(8) + ".tmp";
+ final String msg = String.format("deploying %s to %s:%s", new Object[]{url, getHostname(), dest});
+ log.info(entity + " " + msg);
+ Tasks.setBlockingDetails(msg);
+ try {
+ final String copyTaskMsg = String.format("copying %s to %s:%s", new Object[]{url, getHostname(), tmpDest});
+ DynamicTasks.queue(copyTaskMsg, new Runnable() {
+ @Override
+ public void run() {
+ int result = copyResource(url, tmpDest);
+ if (result != 0) {
- throw new IllegalStateException("Invalud result " + result + " while " + copyTaskMsg);
++ throw new IllegalStateException("Invalid result " + result + " while " + copyTaskMsg);
+ }
+ }
+ });
+
+ // create a backup
+ DynamicTasks.queue(SshTasks.newSshExecTaskFactory(getMachine(), String.format("mv -f %s %s.bak", dest, dest))
+ .allowingNonZeroExitCode());
+
+ //rename temporary upload file to .war to be picked up for deployment
+ DynamicTasks.queue(SshTasks.newSshExecTaskFactory(getMachine(), String.format("mv -f %s %s", tmpDest, dest))
+ .requiringExitCodeZero());
+ log.debug("{} deployed {} to {}:{}", new Object[]{entity, url, getHostname(), dest});
+
+ DynamicTasks.waitForLast();
+ } finally {
+ Tasks.resetBlockingDetails();
+ }
+ return getFilenameContextMapper().convertDeploymentTargetNameToContext(canonicalTargetName);
+ }
+
+ @Override
+ public void undeploy(String targetName) {
+ String dest = getDeployDir() + "/" + getFilenameContextMapper().convertDeploymentTargetNameToFilename(targetName);
+ log.info("{} undeploying {}:{}", new Object[]{entity, getHostname(), dest});
+ int result = getMachine().execCommands("removing war on undeploy", ImmutableList.of(String.format("rm -f %s", dest)));
+ log.debug("{} undeployed {}:{}: result {}", new Object[]{entity, getHostname(), dest, result});
+ }
+
+ @Override
+ public FilenameToWebContextMapper getFilenameContextMapper() {
+ return new FilenameToWebContextMapper();
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
index 0000000,c977a80..e292550
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java
@@@ -1,0 -1,112 +1,114 @@@
+ /*
+ * 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.entity.webapp.jboss;
+
+ import java.util.LinkedHashMap;
+ import java.util.Map;
+ import java.util.concurrent.TimeUnit;
+
+ import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.entity.java.UsesJmx;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
+ import com.google.common.base.Functions;
+
+ public class JBoss6ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss6Server {
+
+ public static final Logger log = LoggerFactory.getLogger(JBoss6ServerImpl.class);
+
+ private volatile JmxFeed jmxFeed;
+
+ public JBoss6ServerImpl() {
+ this(new LinkedHashMap(), null);
+ }
+
+ public JBoss6ServerImpl(Entity parent) {
+ this(new LinkedHashMap(), parent);
+ }
+
+ public JBoss6ServerImpl(Map flags){
+ this(flags, null);
+ }
+
+ public JBoss6ServerImpl(Map flags, Entity parent) {
+ super(flags, parent);
+ }
+
+ @Override
+ public void connectSensors() {
+ super.connectSensors();
+
+ String requestProcessorMbeanName = "jboss.web:type=GlobalRequestProcessor,name=http-*";
+ String serverMbeanName = "jboss.system:type=Server";
+ boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+
+ if (isJmxEnabled()) {
+ jmxFeed = JmxFeed.builder()
+ .entity(this)
+ .period(500, TimeUnit.MILLISECONDS)
+ .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP)
+ // TODO instead of setting SERVICE_UP directly, want to use equivalent of
+ // addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS).key("serverMBean")...
+ // but not supported in feed?
+ .objectName(serverMbeanName)
+ .attributeName("Started")
+ .onException(Functions.constant(false))
+ .suppressDuplicates(true))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("errorCount")
+ .enabled(retrieveUsageMetrics))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("requestCount")
++ .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT))
+ .enabled(retrieveUsageMetrics))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("processingTime")
+ .enabled(retrieveUsageMetrics))
+ .build();
+ } else {
+ // if not using JMX
+ log.warn(this+" running without JMX monitoring; limited visibility of service available");
+ connectServiceUpIsRunning();
+ }
+ }
+
+ @Override
+ public void disconnectSensors() {
+ super.disconnectSensors();
+ if (jmxFeed != null) jmxFeed.stop();
+ disconnectServiceUpIsRunning();
+ }
+
+ @Override
+ public Class<JBoss6Driver> getDriverInterface() {
+ return JBoss6Driver.class;
+ }
+
+ protected boolean isJmxEnabled() {
+ return (this instanceof UsesJmx) && Boolean.TRUE.equals(getConfig(UsesJmx.USE_JMX));
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
index 0000000,e2411a7..10b9564
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java
@@@ -1,0 -1,212 +1,214 @@@
+ /*
+ * 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.entity.webapp.jboss;
+
+ import java.util.Map;
+
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.core.config.render.RendererHints;
+ import org.apache.brooklyn.core.entity.Attributes;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
+ import org.apache.brooklyn.enricher.stock.Enrichers;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.http.HttpFeed;
+ import org.apache.brooklyn.feed.http.HttpPollConfig;
+ import org.apache.brooklyn.feed.http.HttpValueFunctions;
+ import org.apache.brooklyn.util.guava.Functionals;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
+ import com.google.common.base.Functions;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.net.HostAndPort;
+
+ public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss7Server {
+
+ public static final Logger log = LoggerFactory.getLogger(JBoss7ServerImpl.class);
+
+ private volatile HttpFeed httpFeed;
+
+ public JBoss7ServerImpl(){
+ super();
+ }
+
+ public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags){
+ this(flags, null);
+ }
+
+ public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
+ super(flags, parent);
+ }
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return JBoss7Driver.class;
+ }
+
+ @Override
+ public JBoss7Driver getDriver() {
+ return (JBoss7Driver) super.getDriver();
+ }
+
+ static {
+ RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl());
+ }
+
+ @Override
+ protected void connectSensors() {
+ super.connectSensors();
+
+ HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this,
+ getAttribute(MANAGEMENT_HTTP_PORT) + getConfig(PORT_INCREMENT));
+
+ String managementUri = String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource",
+ hp.getHostText(), hp.getPort());
+ sensors().set(MANAGEMENT_URL, managementUri);
+
+ if (isHttpMonitoringEnabled()) {
+ log.debug("JBoss sensors for "+this+" reading from "+managementUri);
+ Map<String, String> includeRuntimeUriVars = ImmutableMap.of("include-runtime","true");
+ boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+
+ httpFeed = HttpFeed.builder()
+ .entity(this)
+ .period(200)
+ .baseUri(managementUri)
+ .credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
+ .poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS)
+ .onSuccess(HttpValueFunctions.responseCode())
+ .suppressDuplicates(true))
+ .poll(new HttpPollConfig<Boolean>(MANAGEMENT_URL_UP)
+ .onSuccess(HttpValueFunctions.responseCodeEquals(200))
+ .onFailureOrException(Functions.constant(false))
+ .suppressDuplicates(true))
+ .poll(new HttpPollConfig<Integer>(REQUEST_COUNT)
+ .vars(includeRuntimeUriVars)
+ .onSuccess(HttpValueFunctions.jsonContents("requestCount", Integer.class))
++ .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT))
+ .enabled(retrieveUsageMetrics))
+ .poll(new HttpPollConfig<Integer>(ERROR_COUNT)
+ .vars(includeRuntimeUriVars)
+ .onSuccess(HttpValueFunctions.jsonContents("errorCount", Integer.class))
+ .enabled(retrieveUsageMetrics))
+ .poll(new HttpPollConfig<Integer>(TOTAL_PROCESSING_TIME)
+ .vars(includeRuntimeUriVars)
+ .onSuccess(HttpValueFunctions.jsonContents("processingTime", Integer.class))
+ .enabled(retrieveUsageMetrics))
+ .poll(new HttpPollConfig<Integer>(MAX_PROCESSING_TIME)
+ .vars(includeRuntimeUriVars)
+ .onSuccess(HttpValueFunctions.jsonContents("maxTime", Integer.class))
+ .enabled(retrieveUsageMetrics))
+ .poll(new HttpPollConfig<Long>(BYTES_RECEIVED)
+ .vars(includeRuntimeUriVars)
+ // jboss seems to report 0 even if it has received lots of requests; dunno why.
+ .onSuccess(HttpValueFunctions.jsonContents("bytesReceived", Long.class))
+ .enabled(retrieveUsageMetrics))
+ .poll(new HttpPollConfig<Long>(BYTES_SENT)
+ .vars(includeRuntimeUriVars)
+ .onSuccess(HttpValueFunctions.jsonContents("bytesSent", Long.class))
+ .enabled(retrieveUsageMetrics))
+ .build();
+
+ enrichers().add(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+ .from(MANAGEMENT_URL_UP)
+ .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
+ .build());
+ }
+
+ connectServiceUpIsRunning();
+ }
+
+ /**
+ * @deprecated since 0.9.0; now a no-op; marked final to force anyone sub-classing + overriding it to update their code.
+ */
+ @Deprecated
+ protected final void connectServiceUp() {
+ }
+
+ /**
+ * @deprecated since 0.9.0; now a no-op; marked final to force anyone sub-classing + overriding it to update their code.
+ */
+ @Deprecated
+ protected final void disconnectServiceUp() {
+ }
+
+ @Override
+ protected void disconnectSensors() {
+ super.disconnectSensors();
+
+ if (httpFeed != null) httpFeed.stop();
+ disconnectServiceUpIsRunning();
+ }
+
+ protected boolean isHttpMonitoringEnabled() {
+ return Boolean.TRUE.equals(getConfig(USE_HTTP_MONITORING));
+ }
+
+ public int getManagementHttpsPort() {
+ return getAttribute(MANAGEMENT_HTTPS_PORT);
+ }
+
+ public int getManagementHttpPort() {
+ return getAttribute(MANAGEMENT_HTTP_PORT);
+ }
+
+ public int getManagementNativePort() {
+ return getAttribute(MANAGEMENT_NATIVE_PORT);
+ }
+
+ public int getPortOffset() {
+ return getConfig(PORT_INCREMENT);
+ }
+
+ public boolean isWelcomeRootEnabled() {
+ return false;
+ }
+
+ public String getBindAddress() {
+ return getConfig(BIND_ADDRESS);
+ }
+
+ public String getManagementBindAddress() {
+ return getConfig(BIND_ADDRESS);
+ }
+
+ public String getUnsecureBindAddress() {
+ return getConfig(BIND_ADDRESS);
+ }
+
+ // If empty-string, disables Management security (!) by excluding the security-realm attribute
+ public String getHttpManagementInterfaceSecurityRealm() {
+ return "";
+ }
+
+ public int getDeploymentTimeoutSecs() {
+ return getConfig(DEPLOYMENT_TIMEOUT);
+ }
+
+ /** Path of the keystore file on the AS7 server */
+ public String getHttpsSslKeystoreFile() {
+ return getDriver().getSslKeystoreFile();
+ }
+
+ @Override
+ public String getShortName() {
+ return "JBossAS7";
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
index 0000000,c772b51..24a6c6f
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java
@@@ -1,0 -1,140 +1,142 @@@
+ /*
+ * 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.entity.webapp.jetty;
+
+ import java.util.concurrent.TimeUnit;
+
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.enricher.stock.Enrichers;
+ import org.apache.brooklyn.entity.java.JavaAppUtils;
+ import org.apache.brooklyn.entity.java.UsesJmx;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
+ import com.google.common.base.Functions;
+ import com.google.common.base.Predicates;
+
+ /**
+ * An {@link org.apache.brooklyn.api.entity.Entity} that represents a single Jetty instance.
+ */
+ public class Jetty6ServerImpl extends JavaWebAppSoftwareProcessImpl implements Jetty6Server {
+
+ private static final Logger log = LoggerFactory.getLogger(Jetty6ServerImpl.class);
+
+ private volatile JmxFeed jmxFeedJetty, jmxFeedMx;
+
+ @Override
+ public void connectSensors() {
+ super.connectSensors();
+
+ if (getDriver().isJmxEnabled()) {
+ String serverMbeanName = "org.mortbay.jetty:type=server,id=0";
+ String statsMbeanName = "org.mortbay.jetty.handler:type=atomicstatisticshandler,id=0";
+
+ jmxFeedJetty = JmxFeed.builder()
+ .entity(this)
+ .period(500, TimeUnit.MILLISECONDS)
+ .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP)
+ .objectName(serverMbeanName)
+ .attributeName("running")
+ .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo(true)))
+ .setOnFailureOrException(false))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+ .objectName(statsMbeanName)
- .attributeName("requests"))
++ .attributeName("requests")
++ .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT)))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_4XX_COUNT)
+ .objectName(statsMbeanName)
+ .attributeName("responses4xx"))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_5XX_COUNT)
+ .objectName(statsMbeanName)
+ .attributeName("responses5xx"))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+ .objectName(statsMbeanName)
+ .attributeName("requestTimeTotal"))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(MAX_PROCESSING_TIME)
+ .objectName(statsMbeanName)
+ .attributeName("requestTimeMax"))
+ // NB: requestsActive may be useful
+ .build();
+
+ enrichers().add(Enrichers.builder()
+ .combining(RESPONSES_4XX_COUNT, RESPONSES_5XX_COUNT)
+ .publishing(ERROR_COUNT)
+ .computingSum()
+ .build());
+
+ jmxFeedMx = JavaAppUtils.connectMXBeanSensors(this);
+ } else {
+ // if not using JMX
+ log.warn("Jetty running without JMX monitoring; limited visibility of service available");
+ // TODO we could do simple things, like check that web server is accepting connections
+ }
+ }
+
+ @Override
+ protected void disconnectSensors() {
+ if (jmxFeedJetty != null) jmxFeedJetty.stop();
+ if (jmxFeedMx != null) jmxFeedMx.stop();
+ super.disconnectSensors();
+ }
+
+ public Integer getJmxPort() {
+ if (((Jetty6Driver) getDriver()).isJmxEnabled()) {
+ return getAttribute(UsesJmx.JMX_PORT);
+ } else {
+ return Integer.valueOf(-1);
+ }
+ }
+
+ @Override
+ public Class getDriverInterface() {
+ return Jetty6Driver.class;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Jetty";
+ }
+
+ @Override
+ public void deploy(String url, String targetName) {
+ super.deploy(url, targetName);
+ restartIfRunning();
+ }
+
+ @Override
+ public void undeploy(String targetName) {
+ super.undeploy(targetName);
+ restartIfRunning();
+ }
+
+ protected void restartIfRunning() {
+ // TODO for now we simply restart jetty to achieve "hot deployment"; should use the config mechanisms
+ Lifecycle serviceState = getAttribute(SERVICE_STATE_ACTUAL);
+ if (serviceState == Lifecycle.RUNNING)
+ restart();
+ // may need a restart also if deploy effector is done in parallel to starting
+ // but note this routine is used by initialDeployWars so just being in starting state is not enough!
+ }
+
+ }
+
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
----------------------------------------------------------------------
diff --cc brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
index 0000000,22cab1f..6302278
mode 000000,100644..100644
--- a/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
+++ b/brooklyn-library/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
@@@ -1,0 -1,119 +1,125 @@@
+ /*
+ * 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.entity.webapp.tomcat;
+
+ import static java.lang.String.format;
+
+ import java.util.concurrent.TimeUnit;
+
++import javax.annotation.Nullable;
++
++import org.apache.brooklyn.api.sensor.AttributeSensor;
++import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.entity.java.JavaAppUtils;
+ import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
+ import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
+ import org.apache.brooklyn.feed.jmx.JmxFeed;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
++import com.google.common.base.Function;
+ import com.google.common.base.Functions;
+ import com.google.common.base.Predicates;
+
+ /**
+ * An {@link org.apache.brooklyn.api.entity.Entity} that represents a single Tomcat instance.
+ */
+ public class TomcatServerImpl extends JavaWebAppSoftwareProcessImpl implements TomcatServer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TomcatServerImpl.class);
+
+ public TomcatServerImpl() {
+ super();
+ }
+
+ private volatile JmxFeed jmxWebFeed;
+ private volatile JmxFeed jmxAppFeed;
+
+ @Override
+ public void connectSensors() {
+ super.connectSensors();
+
+ if (getDriver().isJmxEnabled()) {
+ String requestProcessorMbeanName = "Catalina:type=GlobalRequestProcessor,name=\"http-*\"";
+
+ Integer port = isHttpsEnabled() ? getAttribute(HTTPS_PORT) : getAttribute(HTTP_PORT);
+ String connectorMbeanName = format("Catalina:type=Connector,port=%s", port);
+ boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
+
+ jmxWebFeed = JmxFeed.builder()
+ .entity(this)
+ .period(3000, TimeUnit.MILLISECONDS)
+ .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_PROCESS_IS_RUNNING)
+ // TODO Want to use something different from SERVICE_PROCESS_IS_RUNNING,
+ // to indicate this is jmx MBean's reported state (or failure to connect)
+ .objectName(connectorMbeanName)
+ .attributeName("stateName")
+ .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo("STARTED")))
+ .setOnFailureOrException(false)
+ .suppressDuplicates(true))
+ .pollAttribute(new JmxAttributePollConfig<String>(CONNECTOR_STATUS)
+ .objectName(connectorMbeanName)
+ .attributeName("stateName")
+ .suppressDuplicates(true))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("errorCount")
+ .enabled(retrieveUsageMetrics))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("requestCount")
- .enabled(retrieveUsageMetrics))
++ .enabled(retrieveUsageMetrics)
++ .onFailureOrException(EntityFunctions.attribute(this, REQUEST_COUNT)))
+ .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
+ .objectName(requestProcessorMbeanName)
+ .attributeName("processingTime")
+ .enabled(retrieveUsageMetrics))
+ .build();
+
+ jmxAppFeed = JavaAppUtils.connectMXBeanSensors(this);
+ } else {
+ // if not using JMX
+ LOG.warn("Tomcat running without JMX monitoring; limited visibility of service available");
+ connectServiceUpIsRunning();
+ }
+ }
+
+ @Override
+ public void disconnectSensors() {
+ super.disconnectSensors();
+ if (getDriver() != null && getDriver().isJmxEnabled()) {
+ if (jmxWebFeed != null) jmxWebFeed.stop();
+ if (jmxAppFeed != null) jmxAppFeed.stop();
+ } else {
+ disconnectServiceUpIsRunning();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Class getDriverInterface() {
+ return TomcatDriver.class;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Tomcat";
+ }
+ }
+