http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/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 deleted file mode 100644 index b485449..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * 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 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.apache.brooklyn.management.SubscriptionHandle; -import org.apache.brooklyn.policy.PolicySpec; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.enricher.Enrichers; -import brooklyn.entity.Entity; -import brooklyn.entity.Group; -import brooklyn.entity.annotation.Effector; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.Lifecycle; -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.event.feed.http.HttpValueFunctions; -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; - -/** - * Implementation of the {@link NginxController} entity. - */ -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() { - NginxSshDriver driver = (NginxSshDriver)getDriver(); - if (driver==null) { - Lifecycle state = getAttribute(NginxController.SERVICE_STATE_ACTUAL); - throw new IllegalStateException("Cannot reload (no driver instance; stopped? (state="+state+")"); - } - - driver.reload(); - } - - @Override - public boolean isSticky() { - 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); - - // "up" is defined as returning a valid HTTP response from nginx (including a 404 etc) - httpFeed = addFeed(HttpFeed.builder() - .uniqueTag("nginx-poll") - .entity(this) - .period(getConfig(HTTP_POLL_PERIOD)) - .baseUri(new UrlInferencer(null)) - .poll(new HttpPollConfig<Boolean>(NGINX_URL_ANSWERS_NICELY) - // Any response from Nginx is good. - .checkSuccess(Predicates.alwaysTrue()) - // Accept any nginx response (don't assert specific version), so that sub-classing - // for a custom nginx build is not strict about custom version numbers in headers - .onResult(HttpValueFunctions.containsHeader("Server")) - .setOnException(false) - .suppressDuplicates(true)) - .build()); - - // TODO PERSISTENCE WORKAROUND kept anonymous function in case referenced in persisted state - new Function<HttpToolResponse, Boolean>() { - @Override - public Boolean apply(HttpToolResponse input) { - // Accept any nginx response (don't assert specific version), so that sub-classing - // for a custom nginx build is not strict about custom version numbers in headers - List<String> actual = input.getHeaderLists().get("Server"); - return actual != null && actual.size() == 1; - } - }; - - if (!Lifecycle.RUNNING.equals(getAttribute(SERVICE_STATE_ACTUAL))) { - // TODO when updating the map, if it would change from empty to empty on a successful run - // gate with the above check to prevent flashing on ON_FIRE during rebind (this is invoked on rebind as well as during start) - 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()); - connectServiceUpIsRunning(); - - // Can guarantee that parent/managementContext has been set - Group urlMappings = getConfig(URL_MAPPINGS); - if (urlMappings!=null && urlMappingsMemberTrackerPolicy==null) { - // Listen to the targets of each url-mapping changing - targetAddressesHandler = subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() { - @Override public void onEvent(SensorEvent<Collection<String>> event) { - updateNeeded(); - } - }); - - // Listen to url-mappings being added and removed - urlMappingsMemberTrackerPolicy = addPolicy(PolicySpec.create(UrlMappingsMemberTrackerPolicy.class) - .configure("group", urlMappings)); - } - } - - 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; - } - } - - public static class UrlMappingsMemberTrackerPolicy extends AbstractMembershipTrackingPolicy { - @Override - protected void onEntityEvent(EventType type, Entity entity) { - // relies on policy-rebind injecting the implementation rather than the dynamic-proxy - ((NginxControllerImpl)super.entity).updateNeeded(); - } - } - - @Override - protected void preStop() { - super.preStop(); - removeUrlMappingsMemberTrackerPolicy(); - } - - @Override - protected void postStop() { - // TODO don't want stop to race with the last poll. - super.postStop(); - setAttribute(SERVICE_UP, false); - } - - @Override - protected void disconnectSensors() { - if (httpFeed != null) httpFeed.stop(); - disconnectServiceUpIsRunning(); - super.disconnectSensors(); - } - - @Override - public Class<?> getDriverInterface() { - return NginxDriver.class; - } - - @Override - public NginxDriver getDriver() { - return (NginxDriver) super.getDriver(); - } - - public void doExtraConfigurationDuringStart() { - computePortsAndUrls(); - reconfigureService(); - // reconnect sensors if ports have changed - connectSensors(); - } - - @Override - @Effector(description="Gets the current server configuration (by brooklyn recalculating what the config should be); does not affect the server") - public String getCurrentConfiguration() { - return getConfigFile(); - } - - @Override - @Effector(description="Deploys an archive of static content to the server") - public void deploy(String archiveUrl) { - NginxSshDriver driver = (NginxSshDriver) getDriver(); - if (driver==null) { - if (LOG.isDebugEnabled()) - LOG.debug("No driver for {}, so not deploying archive (is entity stopping? state={})", - this, getAttribute(NginxController.SERVICE_STATE_ACTUAL)); - return; - } - - // Copy to the destination machine and extract contents - ArchiveUtils.deploy(archiveUrl, driver.getMachine(), driver.getRunDir()); - } - - @Override - public void reconfigureService() { - String cfg = getConfigFile(); - if (cfg == null) return; - - if (LOG.isDebugEnabled()) LOG.debug("Reconfiguring {}, targetting {} and {}", new Object[] {this, getServerPoolAddresses(), getUrlMappings()}); - if (LOG.isTraceEnabled()) LOG.trace("Reconfiguring {}, config file:\n{}", this, cfg); - - NginxSshDriver driver = (NginxSshDriver) getDriver(); - if (!driver.isCustomizationCompleted()) { - if (LOG.isDebugEnabled()) LOG.debug("Reconfiguring {}, but driver's customization not yet complete so aborting", this); - return; - } - - driver.getMachine().copyTo(Streams.newInputStreamWithContents(cfg), driver.getRunDir()+"/conf/server.conf"); - - installSslKeys("global", getSslConfig()); - - for (UrlMapping mapping : getUrlMappings()) { - //cache ensures only the first is installed, which is what is assumed below - installSslKeys(mapping.getDomain(), mapping.getConfig(UrlMapping.SSL_CONFIG)); - } - } - - /** - * Installs SSL keys named as {@code id.crt} and {@code id.key} where nginx can find them. - * <p> - * Currently skips re-installs (does not support changing) - */ - public void installSslKeys(String id, ProxySslConfig ssl) { - if (ssl == null) return; - - if (installedKeysCache.contains(id)) return; - - NginxSshDriver driver = (NginxSshDriver) getDriver(); - - if (!Strings.isEmpty(ssl.getCertificateSourceUrl())) { - String certificateDestination = Strings.isEmpty(ssl.getCertificateDestination()) ? driver.getRunDir() + "/conf/" + id + ".crt" : ssl.getCertificateDestination(); - driver.getMachine().copyTo(ImmutableMap.of("permissions", "0600"), - ResourceUtils.create(this).getResourceFromUrl(ssl.getCertificateSourceUrl()), - certificateDestination); - } - - if (!Strings.isEmpty(ssl.getKeySourceUrl())) { - String keyDestination = Strings.isEmpty(ssl.getKeyDestination()) ? driver.getRunDir() + "/conf/" + id + ".key" : ssl.getKeyDestination(); - driver.getMachine().copyTo(ImmutableMap.of("permissions", "0600"), - ResourceUtils.create(this).getResourceFromUrl(ssl.getKeySourceUrl()), - keyDestination); - } - - installedKeysCache.add(id); - } - - @Override - public String getConfigFile() { - NginxSshDriver driver = (NginxSshDriver) getDriver(); - if (driver==null) { - LOG.debug("No driver for {}, so not generating config file (is entity stopping? state={})", - this, getAttribute(NginxController.SERVICE_STATE_ACTUAL)); - return null; - } - - NginxConfigFileGenerator templateGenerator = getConfig(NginxController.SERVER_CONF_GENERATOR); - return templateGenerator.generateConfigFile(driver, this); - } - - @Override - public Iterable<UrlMapping> getUrlMappings() { - // For mapping by URL - Group urlMappingGroup = getConfig(NginxController.URL_MAPPINGS); - if (urlMappingGroup != null) { - return Iterables.filter(urlMappingGroup.getMembers(), UrlMapping.class); - } else { - return Collections.<UrlMapping>emptyList(); - } - } - - @Override - public String getShortName() { - return "Nginx"; - } - - public boolean appendSslConfig(String id, - StringBuilder out, - String prefix, - ProxySslConfig ssl, - boolean sslBlock, - boolean certificateBlock) { - if (ssl == null) - return false; - if (sslBlock) { - out.append(prefix); - out.append("ssl on;\n"); - } - if (ssl.getReuseSessions()) { - out.append(prefix); - out.append("proxy_ssl_session_reuse on;"); - } - if (certificateBlock) { - String cert; - if (Strings.isEmpty(ssl.getCertificateDestination())) { - cert = "" + id + ".crt"; - } else { - cert = ssl.getCertificateDestination(); - } - - out.append(prefix); - out.append("ssl_certificate " + cert + ";\n"); - - String key; - if (!Strings.isEmpty(ssl.getKeyDestination())) { - key = ssl.getKeyDestination(); - } else if (!Strings.isEmpty(ssl.getKeySourceUrl())) { - key = "" + id + ".key"; - } else { - key = null; - } - - if (key != null) { - out.append(prefix); - out.append("ssl_certificate_key " + key + ";\n"); - } - } - return true; - } -}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDefaultConfigGenerator.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDefaultConfigGenerator.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDefaultConfigGenerator.java deleted file mode 100644 index 1ed7e49..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDefaultConfigGenerator.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import static java.lang.String.format; - -import java.util.Collection; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; - -import brooklyn.entity.proxy.ProxySslConfig; -import brooklyn.util.text.Strings; - -/** - * Generates the {@code server.conf} configuration file using sensors on an {@link NginxController}. - */ -public class NginxDefaultConfigGenerator implements NginxConfigFileGenerator { - - private static final Logger LOG = LoggerFactory.getLogger(NginxDefaultConfigGenerator.class); - - public NginxDefaultConfigGenerator() { } - - @Override - public String generateConfigFile(NginxDriver driver, NginxController nginx) { - StringBuilder config = new StringBuilder(); - config.append("\n"); - config.append(format("pid %s;\n", driver.getPidFile())); - config.append("events {\n"); - config.append(" worker_connections 8196;\n"); - config.append("}\n"); - config.append("http {\n"); - - ProxySslConfig globalSslConfig = nginx.getSslConfig(); - - if (nginx.isSsl()) { - verifyConfig(globalSslConfig); - appendSslConfig("global", config, " ", globalSslConfig, true, true); - } - - // If no servers, then defaults to returning 404 - // TODO Give nicer page back - if (nginx.getDomain()!=null || nginx.getServerPoolAddresses() == null || nginx.getServerPoolAddresses().isEmpty()) { - config.append(" server {\n"); - config.append(getCodeForServerConfig()); - config.append(" listen "+nginx.getPort()+";\n"); - config.append(getCodeFor404()); - config.append(" }\n"); - } - - // For basic round-robin across the server-pool - if (nginx.getServerPoolAddresses() != null && nginx.getServerPoolAddresses().size() > 0) { - config.append(format(" upstream "+nginx.getId()+" {\n")); - if (nginx.isSticky()){ - config.append(" sticky;\n"); - } - for (String address : nginx.getServerPoolAddresses()) { - config.append(" server "+address+";\n"); - } - config.append(" }\n"); - config.append(" server {\n"); - config.append(getCodeForServerConfig()); - config.append(" listen "+nginx.getPort()+";\n"); - if (nginx.getDomain()!=null) - config.append(" server_name "+nginx.getDomain()+";\n"); - config.append(" location / {\n"); - config.append(" proxy_pass "+(globalSslConfig != null && globalSslConfig.getTargetIsSsl() ? "https" : "http")+"://"+nginx.getId()+";\n"); - config.append(" }\n"); - config.append(" }\n"); - } - - // For mapping by URL - Iterable<UrlMapping> mappings = nginx.getUrlMappings(); - Multimap<String, UrlMapping> mappingsByDomain = LinkedHashMultimap.create(); - for (UrlMapping mapping : mappings) { - Collection<String> addrs = mapping.getAttribute(UrlMapping.TARGET_ADDRESSES); - if (addrs != null && addrs.size() > 0) { - mappingsByDomain.put(mapping.getDomain(), mapping); - } - } - - for (UrlMapping um : mappings) { - Collection<String> addrs = um.getAttribute(UrlMapping.TARGET_ADDRESSES); - if (addrs != null && addrs.size() > 0) { - config.append(format(" upstream "+um.getUniqueLabel()+" {\n")); - if (nginx.isSticky()){ - config.append(" sticky;\n"); - } - for (String address: addrs) { - config.append(" server "+address+";\n"); - } - config.append(" }\n"); - } - } - - for (String domain : mappingsByDomain.keySet()) { - config.append(" server {\n"); - config.append(getCodeForServerConfig()); - config.append(" listen "+nginx.getPort()+";\n"); - config.append(" server_name "+domain+";\n"); - boolean hasRoot = false; - - // set up SSL - ProxySslConfig localSslConfig = null; - for (UrlMapping mappingInDomain : mappingsByDomain.get(domain)) { - ProxySslConfig sslConfig = mappingInDomain.getConfig(UrlMapping.SSL_CONFIG); - if (sslConfig!=null) { - verifyConfig(sslConfig); - if (localSslConfig!=null) { - if (localSslConfig.equals(sslConfig)) { - //ignore identical config specified on multiple mappings - } else { - LOG.warn("{} mapping {} provides SSL config for {} when a different config had already been provided by another mapping, ignoring this one", - new Object[] {this, mappingInDomain, domain}); - } - } else if (globalSslConfig!=null) { - if (globalSslConfig.equals(sslConfig)) { - //ignore identical config specified on multiple mappings - } else { - LOG.warn("{} mapping {} provides SSL config for {} when a different config had been provided at root nginx scope, ignoring this one", - new Object[] {this, mappingInDomain, domain}); - } - } else { - //new config, is okay - localSslConfig = sslConfig; - } - } - } - if (localSslConfig != null) { - appendSslConfig(domain, config, " ", localSslConfig, true, true); - } - - for (UrlMapping mappingInDomain : mappingsByDomain.get(domain)) { - // TODO Currently only supports "~" for regex. Could add support for other options, - // such as "~*", "^~", literals, etc. - boolean isRoot = mappingInDomain.getPath()==null || mappingInDomain.getPath().length()==0 || mappingInDomain.getPath().equals("/"); - if (isRoot && hasRoot) { - LOG.warn(""+this+" mapping "+mappingInDomain+" provides a duplicate / proxy, ignoring"); - } else { - hasRoot |= isRoot; - String location = isRoot ? "/" : "~ " + mappingInDomain.getPath(); - config.append(" location "+location+" {\n"); - Collection<UrlRewriteRule> rewrites = mappingInDomain.getConfig(UrlMapping.REWRITES); - if (rewrites != null && rewrites.size() > 0) { - for (UrlRewriteRule rule: rewrites) { - config.append(" rewrite \"^"+rule.getFrom()+"$\" \""+rule.getTo()+"\""); - if (rule.isBreak()) config.append(" break"); - config.append(" ;\n"); - } - } - config.append(" proxy_pass "+ - (localSslConfig != null && localSslConfig.getTargetIsSsl() ? "https" : - (localSslConfig == null && globalSslConfig != null && globalSslConfig.getTargetIsSsl()) ? "https" : - "http")+ - "://"+mappingInDomain.getUniqueLabel()+" ;\n"); - config.append(" }\n"); - } - } - if (!hasRoot) { - //provide a root block giving 404 if there isn't one for this server - config.append(" location / { \n"+getCodeFor404()+" }\n"); - } - config.append(" }\n"); - } - - config.append("}\n"); - - return config.toString(); - } - - protected String getCodeForServerConfig() { - // See http://wiki.nginx.org/HttpProxyModule - return ""+ - // this prevents nginx from reporting version number on error pages - " server_tokens off;\n"+ - - // this prevents nginx from using the internal proxy_pass codename as Host header passed upstream. - // Not using $host, as that causes integration test to fail with a "connection refused" testing - // url-mappings, at URL "http://localhost:${port}/atC0" (with a trailing slash it does work). - " proxy_set_header Host $http_host;\n"+ - - // following added, as recommended for wordpress in: - // http://zeroturnaround.com/labs/wordpress-protips-go-with-a-clustered-approach/#!/ - " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n"+ - " proxy_set_header X-Real-IP $remote_addr;\n"; - } - - protected String getCodeFor404() { - return " return 404;\n"; - } - - protected void verifyConfig(ProxySslConfig proxySslConfig) { - if(Strings.isEmpty(proxySslConfig.getCertificateDestination()) && Strings.isEmpty(proxySslConfig.getCertificateSourceUrl())){ - throw new IllegalStateException("ProxySslConfig can't have a null certificateDestination and null certificateSourceUrl. One or both need to be set"); - } - } - - protected boolean appendSslConfig(String id, StringBuilder out, String prefix, ProxySslConfig ssl, - boolean sslBlock, boolean certificateBlock) { - if (ssl == null) return false; - if (sslBlock) { - out.append(prefix); - out.append("ssl on;\n"); - } - if (ssl.getReuseSessions()) { - out.append(prefix); - out.append(""); - } - if (certificateBlock) { - String cert; - if (Strings.isEmpty(ssl.getCertificateDestination())) { - cert = "" + id + ".crt"; - } else { - cert = ssl.getCertificateDestination(); - } - - out.append(prefix); - out.append("ssl_certificate " + cert + ";\n"); - - String key; - if (!Strings.isEmpty(ssl.getKeyDestination())) { - key = ssl.getKeyDestination(); - } else if (!Strings.isEmpty(ssl.getKeySourceUrl())) { - key = "" + id + ".key"; - } else { - key = null; - } - - if (key != null) { - out.append(prefix); - out.append("ssl_certificate_key " + key + ";\n"); - } - - out.append("ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n"); - } - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDriver.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDriver.java deleted file mode 100644 index 1ef651f..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxDriver.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import brooklyn.entity.basic.SoftwareProcessDriver; - -public interface NginxDriver extends SoftwareProcessDriver { - - String getRunDir(); - - String getPidFile(); - - boolean isCustomizationCompleted(); - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/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 deleted file mode 100644 index 023067a..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import static java.lang.String.format; - -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.brooklyn.management.ManagementContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.Entities; -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.util.collections.MutableMap; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.net.Networking; -import brooklyn.util.os.Os; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.stream.Streams; -import brooklyn.util.task.DynamicTasks; -import brooklyn.util.task.Tasks; -import brooklyn.util.task.ssh.SshTasks; -import brooklyn.util.task.ssh.SshTasks.OnFailingTask; -import brooklyn.util.text.Strings; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; - -/** - * Start a {@link NginxController} in a {@link brooklyn.location.Location} accessible over ssh. - */ -public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements NginxDriver { - - // TODO An alternative way of installing nginx is described at: - // http://sjp.co.nz/posts/building-nginx-for-debian-systems/ - // It's use of `apt-get source nginx` and `apt-get build-dep nginx` makes - // it look higher level and therefore more appealing. - - public static final Logger log = LoggerFactory.getLogger(NginxSshDriver.class); - public static final String NGINX_PID_FILE = "logs/nginx.pid"; - - private boolean customizationCompleted = false; - - public NginxSshDriver(NginxControllerImpl entity, SshMachineLocation machine) { - super(entity, machine); - - entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFileLocation()); - entity.setAttribute(NginxController.ACCESS_LOG_LOCATION, getAccessLogLocation()); - entity.setAttribute(NginxController.ERROR_LOG_LOCATION, getErrorLogLocation()); - } - - @Override - public NginxControllerImpl getEntity() { - return (NginxControllerImpl) super.getEntity(); - } - - public String getLogFileLocation() { - return format("%s/console", getRunDir()); - } - - public String getAccessLogLocation() { - String accessLog = entity.getConfig(NginxController.ACCESS_LOG_LOCATION); - return format("%s/%s", getRunDir(), accessLog); - } - - public String getErrorLogLocation() { - String errorLog = entity.getConfig(NginxController.ERROR_LOG_LOCATION); - return format("%s/%s", getRunDir(), errorLog); - } - - /** By default Nginx writes the pid of the master process to {@code logs/nginx.pid} */ - @Override - public String getPidFile() { - 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; - } - - @Override - public void postLaunch() { - entity.setAttribute(NginxController.PID_FILE, getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME); - 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(); - } - - @Override - public void preInstall() { - resolver = Entities.newDownloader(this); - setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("nginx-%s", getVersion())))); - } - - @Override - public void install() { - // inessential here, installation will fail later if it needs to sudo (eg if using port 80) - DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo(getMachine(), OnFailingTask.WARN_OR_IF_DYNAMIC_FAIL_MARKING_INESSENTIAL)).orSubmitAndBlock(); - - List<String> nginxUrls = resolver.getTargets(); - String nginxSaveAs = resolver.getFilename(); - - boolean sticky = ((NginxController) entity).isSticky(); - boolean isMac = getMachine().getOsDetails().isMac(); - - MutableMap<String, String> installGccPackageFlags = MutableMap.of( - "onlyifmissing", "gcc", - "yum", "gcc", - "apt", "gcc", - "zypper", "gcc", - "port", null); - MutableMap<String, String> installMakePackageFlags = MutableMap.of( - "onlyifmissing", "make", - "yum", "make", - "apt", "make", - "zypper", "make", - "port", null); - MutableMap<String, String> installPackageFlags = MutableMap.of( - "yum", "openssl-devel pcre-devel", - "apt", "libssl-dev zlib1g-dev libpcre3-dev", - "zypper", "libopenssl-devel pcre-devel", - "port", null); - - String stickyModuleVersion = entity.getConfig(NginxController.STICKY_VERSION); - DownloadResolver stickyModuleResolver = mgmt().getEntityDownloadsManager().newDownloader( - this, "stickymodule", ImmutableMap.of("addonversion", stickyModuleVersion)); - List<String> stickyModuleUrls = stickyModuleResolver.getTargets(); - String stickyModuleSaveAs = stickyModuleResolver.getFilename(); - String stickyModuleExpandedInstallDir = String.format("%s/src/%s", getExpandedInstallDir(), - stickyModuleResolver.getUnpackedDirectoryName("nginx-sticky-module-"+stickyModuleVersion)); - - List<String> cmds = Lists.newArrayList(); - - cmds.add(BashCommands.INSTALL_TAR); - cmds.add(BashCommands.alternatives( - BashCommands.ifExecutableElse0("apt-get", BashCommands.installPackage("build-essential")), - BashCommands.ifExecutableElse0("yum", BashCommands.sudo("yum -y --nogpgcheck groupinstall \"Development Tools\"")))); - cmds.add(BashCommands.installPackage(installGccPackageFlags, "nginx-prerequisites-gcc")); - cmds.add(BashCommands.installPackage(installMakePackageFlags, "nginx-prerequisites-make")); - cmds.add(BashCommands.installPackage(installPackageFlags, "nginx-prerequisites")); - cmds.addAll(BashCommands.commandsToDownloadUrlsAs(nginxUrls, nginxSaveAs)); - - String pcreExpandedInstallDirname = ""; - if (isMac) { - String pcreVersion = entity.getConfig(NginxController.PCRE_VERSION); - DownloadResolver pcreResolver = mgmt().getEntityDownloadsManager().newDownloader( - this, "pcre", ImmutableMap.of("addonversion", pcreVersion)); - List<String> pcreUrls = pcreResolver.getTargets(); - String pcreSaveAs = pcreResolver.getFilename(); - pcreExpandedInstallDirname = pcreResolver.getUnpackedDirectoryName("pcre-"+pcreVersion); - - // Install PCRE - cmds.addAll(BashCommands.commandsToDownloadUrlsAs(pcreUrls, pcreSaveAs)); - cmds.add(format("mkdir -p %s/pcre-dist", getInstallDir())); - cmds.add(format("tar xvzf %s", pcreSaveAs)); - cmds.add(format("cd %s", pcreExpandedInstallDirname)); - cmds.add(format("./configure --prefix=%s/pcre-dist", getInstallDir())); - cmds.add("make"); - cmds.add("make install"); - cmds.add("cd .."); - } - - cmds.add(format("tar xvzf %s", nginxSaveAs)); - cmds.add(format("cd %s", getExpandedInstallDir())); - - if (sticky) { - // Latest versions of sticky module expand to a different folder than the file name. - // Extract to folder set by us so we know where the sources are. - cmds.add(format("mkdir -p %s", stickyModuleExpandedInstallDir)); - cmds.add(format("pushd %s", stickyModuleExpandedInstallDir)); - cmds.addAll(BashCommands.commandsToDownloadUrlsAs(stickyModuleUrls, stickyModuleSaveAs)); - cmds.add(format("tar --strip-component=1 -xvzf %s", stickyModuleSaveAs)); - cmds.add("popd"); - } - - // Note that for OS X, not including space after "-L" because broken in 10.6.8 (but fixed in 10.7.x) - // see http://trac.nginx.org/nginx/ticket/227 - String withLdOpt = entity.getConfig(NginxController.WITH_LD_OPT); - if (isMac) withLdOpt = format("-L%s/pcre-dist/lib", getInstallDir()) + (Strings.isBlank(withLdOpt) ? "" : " " + withLdOpt); - String withCcOpt = entity.getConfig(NginxController.WITH_CC_OPT); - - if (isMac) { - // TODO Upgrade sticky module as soon as a fix for https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/issue/16/can-not-compile-on-macosx-yosemite - // is released and remove this block. - withCcOpt = (Strings.isBlank(withCcOpt) ? "" : (withCcOpt + " ")) + "-Wno-error"; - } - - StringBuilder configureCommand = new StringBuilder("./configure") - .append(format(" --prefix=%s/dist", getExpandedInstallDir())) - .append(" --with-http_ssl_module") - .append(sticky ? format(" --add-module=%s ", stickyModuleExpandedInstallDir) : "") - .append(!Strings.isBlank(withLdOpt) ? format(" --with-ld-opt=\"%s\"", withLdOpt) : "") - .append(!Strings.isBlank(withCcOpt) ? format(" --with-cc-opt=\"%s\"", withCcOpt) : "") - ; - if (isMac) { - configureCommand.append(" --with-pcre=") - .append(getInstallDir()).append("/").append(pcreExpandedInstallDirname); - } - - cmds.addAll(ImmutableList.of( - "mkdir -p dist", - configureCommand.toString(), - "make install")); - - ScriptHelper script = newScript(INSTALLING) - .body.append(cmds) - .header.prepend("set -x") - .gatherOutput() - .failOnNonZeroResultCode(false); - - int result = script.execute(); - - if (result != 0) { - String notes = "likely an error building nginx. consult the brooklyn log ssh output for further details.\n"+ - "note that this Brooklyn nginx driver compiles nginx from source. " + - "it attempts to install common prerequisites but this does not always succeed.\n"; - OsDetails os = getMachine().getOsDetails(); - if (os.isMac()) { - notes += "deploying to Mac OS X, you will require Xcode and Xcode command-line tools, and on " + - "some versions the pcre library (e.g. using macports, sudo port install pcre).\n"; - } - if (os.isWindows()) { - notes += "this nginx driver is not designed for windows, unless cygwin is installed, and you are patient.\n"; - } - if (getEntity().getApplication().getClass().getCanonicalName().startsWith("brooklyn.demo.")) { - // this is maybe naughty ... but since we use nginx in the first demo example, - // and since it's actually pretty complicated, let's give a little extra hand-holding - notes += - "if debugging this is all a bit much and you just want to run a demo, " + - "you have two fairly friendly options.\n" + - "1. you can use a well known cloud, like AWS or Rackspace, where this should run " + - "in a tried-and-tested Ubuntu or CentOS environment, without any problems " + - "(and if it does let us know and we'll fix it!).\n"+ - "2. or you can just use the demo without nginx, instead access the appserver instances directly.\n"; - } - - if (!script.getResultStderr().isEmpty()) { - notes += "\n" + "STDERR\n" + script.getResultStderr()+"\n"; - Streams.logStreamTail(log, "STDERR of problem in "+Tasks.current(), Streams.byteArrayOfString(script.getResultStderr()), 1024); - } - if (!script.getResultStdout().isEmpty()) { - notes += "\n" + "STDOUT\n" + script.getResultStdout()+"\n"; - Streams.logStreamTail(log, "STDOUT of problem in "+Tasks.current(), Streams.byteArrayOfString(script.getResultStdout()), 1024); - } - - Tasks.setExtraStatusDetails(notes.trim()); - - throw new IllegalStateException("Installation of nginx failed (shell returned non-zero result "+result+")"); - } - } - - private ManagementContext mgmt() { - return ((EntityInternal) entity).getManagementContext(); - } - - @Override - public void customize() { - newScript(CUSTOMIZING) - .body.append( - format("mkdir -p %s", getRunDir()), - format("cp -R %s/dist/{conf,html,logs,sbin} %s", getExpandedInstallDir(), getRunDir())) - .execute(); - - // Install static content archive, if specified - String archiveUrl = entity.getConfig(NginxController.STATIC_CONTENT_ARCHIVE_URL); - if (Strings.isNonBlank(archiveUrl)) { - getEntity().deploy(archiveUrl); - } - - customizationCompleted = true; - } - - @Override - public boolean isCustomizationCompleted() { - return customizationCompleted; - } - - @Override - public void launch() { - // 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("port", getPort())); - - getEntity().doExtraConfigurationDuringStart(); - - // We wait for evidence of running because, using - // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool, - // we saw the ssh session return before the tomcat process was fully running - // so the process failed to start. - newScript(MutableMap.of("usePidFile", false), LAUNCHING) - .body.append( - format("cd %s", getRunDir()), - BashCommands.requireExecutable("./sbin/nginx"), - 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" + - " test -f %1$s && ps -p `cat %1$s` && exit\n" + - " sleep 1\n" + - "done\n" + - "echo \"No explicit error launching nginx but couldn't find process by pid; continuing but may subsequently fail\"\n" + - "cat %2$s | tee /dev/stderr", - getPidFile(), getLogFileLocation())) - .execute(); - } - - public static String sudoIfPrivilegedPort(int port, String command) { - return port < 1024 ? BashCommands.sudo(command) : command; - } - - public static String sudoBashCIfPrivilegedPort(int port, String command) { - return port < 1024 ? BashCommands.sudo("bash -c '"+command+"'") : command; - } - - @Override - public boolean isRunning() { - return newScript(MutableMap.of("usePidFile", getPidFile()), CHECK_RUNNING).execute() == 0; - } - - @Override - public void stop() { - // Don't `kill -9`, as that doesn't stop the worker processes - newScript(MutableMap.of("usePidFile", false), STOPPING). - body.append( - format("cd %s", getRunDir()), - format("export PID=`cat %s`", getPidFile()), - "test -n \"$PID\" || exit 0", - sudoIfPrivilegedPort(getPort(), "kill $PID")) - .execute(); - } - - @Override - public void kill() { - stop(); - } - - private final ExecController reloadExecutor = new ExecController( - entity+"->reload", - new Runnable() { - @Override - public void run() { - reloadImpl(); - } - }); - - public void reload() { - // If there are concurrent calls to reload (such that some calls come in when another call is queued), then - // don't bother doing the subsequent calls. Instead just rely on the currently queued call. - // - // Motivation is that calls to nginx.reload were backing up: we ended up executing lots of them in parallel - // when there were several changes to the nginx conifg that requiring a reload. The problem can be particularly - // bad because the ssh commands take a second or two - if 10 changes were made to the config in that time, we'd - // end up executing reload 10 times in parallel. - - reloadExecutor.run(); - } - - private void reloadImpl() { - // Note that previously, if serviceUp==false then we'd restart nginx. - // That caused a race on stop()+reload(): nginx could simultaneously be stopping and also reconfiguring - // (e.g. due to a cluster-resize), the restart() would leave nginx running even after stop() had returned. - // - // Now we rely on NginxController always calling update (and thus reload) once it has started. This is - // done in AbstractController.postActivation(). - // - // If our blocking check sees that !isRunning() (and if a separate thread is starting it, and subsequently - // 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. - - 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; - } - - doReloadNow(); - } - - /** - * Instructs nginx to reload its configuration (without restarting, so don't lose any requests). - * Can be overridden if necessary, to change the call used for reloading. - */ - private void doReloadNow() { - // We use kill -HUP because that is recommended at http://wiki.nginx.org/CommandLine, - // but there is no noticeable difference (i.e. no impact on #365) compared to: - // sudoIfPrivilegedPort(getHttpPort(), format("./sbin/nginx -p %s/ -c conf/server.conf -s reload", getRunDir())) - // - // Note that if conf file is invalid, you'll get no stdout/stderr from `kill` but you - // do from using `nginx ... -s reload` so that can be handy when manually debugging. - - log.debug("reloading nginx by simularing restart (kill -HUP) - {}", entity); - newScript(RESTARTING) - .body.append( - format("cd %s", getRunDir()), - format("export PID=`cat %s`", getPidFile()), - sudoIfPrivilegedPort(getPort(), "kill -HUP $PID")) - .execute(); - } - - /** - * Executes the given task, but only if another thread hasn't executed it for us (where the other thread - * began executing it after the current caller of {@link #run()} began attempting to do so itself). - * - * @author aled - */ - private static class ExecController { - private final String summary; - private final Runnable task; - private final AtomicLong counter = new AtomicLong(); - - ExecController(String summary, Runnable task) { - this.summary = summary; - this.task = task; - } - - void run() { - long preCount = counter.get(); - synchronized (this) { - if (counter.compareAndSet(preCount, preCount+1)) { - try { - if (log.isDebugEnabled()) log.debug("Executing {}; incremented count to {}", new Object[] {summary, counter}); - task.run(); - } catch (Exception e) { - if (log.isDebugEnabled()) log.debug("Failed executing {}; reseting count to {} and propagating exception: {}", new Object[] {summary, preCount, e}); - counter.set(preCount); - throw Exceptions.propagate(e); - } - } else { - if (log.isDebugEnabled()) log.debug("Not executing {} because executed by another thread subsequent to us attempting (preCount {}; count {})", new Object[] {summary, preCount, counter}); - } - } - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxTemplateConfigGenerator.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxTemplateConfigGenerator.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxTemplateConfigGenerator.java deleted file mode 100644 index aea735f..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxTemplateConfigGenerator.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import java.util.Collection; -import java.util.Map; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.proxy.ProxySslConfig; -import brooklyn.util.ResourceUtils; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.text.Strings; -import brooklyn.util.text.TemplateProcessor; - -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; - -/** - * Processes a FreeMarker template to generate the {@code server.conf} configuration file for an - * {@link NginxController}. - * <p> - * Note this must be explicitly enabled via {@link NginxController#SERVER_CONF_GENERATOR}. - */ -public class NginxTemplateConfigGenerator implements NginxConfigFileGenerator { - - public static final ConfigKey<String> SERVER_CONF_TEMPLATE_URL = ConfigKeys.newStringConfigKey( - "nginx.config.templateUrl", "The server.conf configuration file URL (FreeMarker template). " - + "Only applies if 'nginx.config.generator' specifies a generator which uses a template.", - "classpath://brooklyn/entity/proxy/nginx/server.conf"); - - public NginxTemplateConfigGenerator() { } - - @Override - public String generateConfigFile(NginxDriver driver, NginxController nginx) { - // Check template URL exists - String templateUrl = driver.getEntity().getConfig(NginxController.SERVER_CONF_TEMPLATE_URL); - ResourceUtils.create(this).checkUrlExists(templateUrl); - - // Check SSL configuration - ProxySslConfig ssl = driver.getEntity().getConfig(NginxController.SSL_CONFIG); - if (ssl != null && Strings.isEmpty(ssl.getCertificateDestination()) && Strings.isEmpty(ssl.getCertificateSourceUrl())) { - throw new IllegalStateException("ProxySslConfig can't have a null certificateDestination and null certificateSourceUrl. One or both need to be set"); - } - - // For mapping by URL - Iterable<UrlMapping> mappings = ((NginxController) driver.getEntity()).getUrlMappings(); - Multimap<String, UrlMapping> mappingsByDomain = LinkedHashMultimap.create(); - for (UrlMapping mapping : mappings) { - Collection<String> addrs = mapping.getAttribute(UrlMapping.TARGET_ADDRESSES); - if (addrs != null && addrs.size() > 0) { - mappingsByDomain.put(mapping.getDomain(), mapping); - } - } - Map<String, Object> substitutions = MutableMap.<String, Object>builder() - .putIfNotNull("ssl", ssl) - .put("urlMappings", mappings) - .put("domainMappings", mappingsByDomain) - .build(); - - // Get template contents and process - String contents = ResourceUtils.create(driver.getEntity()).getResourceAsString(templateUrl); - return TemplateProcessor.processTemplateContents(contents, driver, substitutions); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/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 deleted file mode 100644 index d8a0377..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMapping.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import java.util.Collection; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.Entity; -import brooklyn.entity.annotation.Effector; -import brooklyn.entity.basic.AbstractGroup; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.MethodEffector; -import brooklyn.entity.proxy.AbstractController; -import brooklyn.entity.proxy.ProxySslConfig; -import brooklyn.entity.proxying.ImplementedBy; -import brooklyn.event.AttributeSensor; -import brooklyn.event.basic.Sensors; -import brooklyn.util.flags.SetFromFlag; - -import com.google.common.reflect.TypeToken; - -/** - * This is a group whose members will be made available to a load-balancer / URL forwarding service (such as nginx). - * Configuration requires a <b>domain</b> and some mechanism for finding members. - * The easiest way to find members is using a <b>target</b> whose children will be tracked, - * but alternative membership policies can also be used. - */ -@ImplementedBy(UrlMappingImpl.class) -public interface UrlMapping extends AbstractGroup { - - MethodEffector<Void> DISCARD = new MethodEffector<Void>(UrlMapping.class, "discard"); - - @SetFromFlag("label") - ConfigKey<String> LABEL = ConfigKeys.newStringConfigKey( - "urlmapping.label", "optional human-readable label to identify a server"); - - @SetFromFlag("domain") - ConfigKey<String> DOMAIN = ConfigKeys.newStringConfigKey( - "urlmapping.domain", "domain (hostname, e.g. www.foo.com) to present for this URL map rule; required."); - - @SetFromFlag("path") - ConfigKey<String> PATH = ConfigKeys.newStringConfigKey( - "urlmapping.path", "URL path (pattern) for this URL map rule. Currently only supporting regex matches "+ - "(if not supplied, will match all paths at the indicated domain)"); - - @SetFromFlag("ssl") - ConfigKey<ProxySslConfig> SSL_CONFIG = AbstractController.SSL_CONFIG; - - @SetFromFlag("rewrites") - @SuppressWarnings("serial") - ConfigKey<Collection<UrlRewriteRule>> REWRITES = ConfigKeys.newConfigKey(new TypeToken<Collection<UrlRewriteRule>>() { }, - "urlmapping.rewrites", "Set of URL rewrite rules to apply"); - - @SetFromFlag("target") - ConfigKey<Entity> TARGET_PARENT = ConfigKeys.newConfigKey(Entity.class, - "urlmapping.target.parent", "optional target entity whose children will be pointed at by this mapper"); - - @SuppressWarnings("serial") - AttributeSensor<Collection<String>> TARGET_ADDRESSES = Sensors.newSensor(new TypeToken<Collection<String>>() { }, - "urlmapping.target.addresses", "set of addresses which should be forwarded to by this URL mapping"); - - String getUniqueLabel(); - - /** Adds a rewrite rule, must be called at config time. See {@link UrlRewriteRule} for more info. */ - UrlMapping addRewrite(String from, String to); - - /** Adds a rewrite rule, must be called at config time. See {@link UrlRewriteRule} for more info. */ - UrlMapping addRewrite(UrlRewriteRule rule); - - String getDomain(); - - String getPath(); - - Entity getTarget(); - - void setTarget(Entity target); - - void recompute(); - - Collection<String> getTargetAddresses(); - - ProxySslConfig getSsl(); - - @Effector(description="Unmanages the url-mapping, so it is discarded and no longer applies") - void discard(); -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java deleted file mode 100644 index 482e317..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import static brooklyn.util.JavaGroovyEquivalents.groovyTruth; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -import org.apache.brooklyn.management.SubscriptionHandle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.Entity; -import brooklyn.entity.basic.AbstractGroupImpl; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.Entities; -import brooklyn.entity.basic.EntityPredicates; -import brooklyn.entity.proxy.ProxySslConfig; -import brooklyn.entity.trait.Changeable; -import brooklyn.entity.trait.Startable; -import brooklyn.entity.webapp.WebAppServiceConstants; -import brooklyn.event.SensorEvent; -import brooklyn.event.SensorEventListener; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; - -/** - * This is a group whose members will be made available to a load-balancer / URL forwarding service (such as nginx). - * <p> - * Configuration requires a <b>domain</b> and some mechanism for finding members. - * The easiest way to find members is using a <b>target</b> whose children will be tracked, - * but alternative membership policies can also be used. - */ -public class UrlMappingImpl extends AbstractGroupImpl implements UrlMapping { - - private static final Logger log = LoggerFactory.getLogger(UrlMapping.class); - - public UrlMappingImpl() { - super(); - } - - @Override - public String getUniqueLabel() { - String l = getConfig(LABEL); - if (groovyTruth(l)) return getId()+"-"+l; - else return getId(); - } - - /** adds a rewrite rule, must be called at config time. see {@link UrlRewriteRule} for more info. */ - @Override - public synchronized UrlMapping addRewrite(String from, String to) { - return addRewrite(new UrlRewriteRule(from, to)); - } - - /** adds a rewrite rule, must be called at config time. see {@link UrlRewriteRule} for more info. */ - @Override - public synchronized UrlMapping addRewrite(UrlRewriteRule rule) { - Collection<UrlRewriteRule> rewrites = getConfig(REWRITES); - if (rewrites==null) { - rewrites = new ArrayList<UrlRewriteRule>(); - } - rewrites.add(rule); - setConfig(REWRITES, rewrites); - return this; - } - - @Override - public String getDomain() { - return Preconditions.checkNotNull( getConfig(DOMAIN), "domain config argument required"); - } - - @Override - public String getPath() { - return getConfig(PATH); - } - - @Override - public Entity getTarget() { - return getConfig(TARGET_PARENT); - } - - @Override - public void setTarget(Entity target) { - setConfig(TARGET_PARENT, target); - recompute(); - } - - @Override - public void onManagementStarting() { - super.onManagementStarting(); - - if (getConfig(TARGET_PARENT) != null) { - recompute(); - // following line could be more efficient (just modify the addresses set, not clearing it each time; - // but since addresses is lazy loaded not that big a deal) - // subscribe(this, Changeable.GROUP_SIZE, { resetAddresses(true) } as SensorEventListener); - // above not needed since our target tracking figures this out - } - } - - /** defines how address string, ie hostname:port, is constructed from a given entity. - * returns null if not possible. - * <p> - * the default is to look at HOSTNAME and HTTPS_PORT or HTTP_PORT attribute sensors (depending on SSL_CONFIG being set with targetIsSsl). - * <p> - * this method is suitable (intended) for overriding if needed. - */ - protected String getAddressOfEntity(Entity s) { - String h = s.getAttribute(Attributes.HOSTNAME); - - Integer p = null; - Set<String> protos = s.getAttribute(WebAppServiceConstants.ENABLED_PROTOCOLS); - ProxySslConfig sslConfig = getConfig(SSL_CONFIG); - if (sslConfig != null && sslConfig.getTargetIsSsl()) { - // use ssl - if (protos != null && hasProtocol(protos, "https")) { - // proto configured correctly - } else { - // proto not defined; use https anyway, but it might fail - log.warn("Misconfiguration for "+this+": ENABLED_PROTOCOLS='"+protos+"' for "+s+" but sslConfig="+sslConfig); - } - p = s.getAttribute(Attributes.HTTPS_PORT); - if (p == null) - log.warn("Misconfiguration for "+this+": sslConfig="+sslConfig+" but no HTTPS_PORT on "+s); - } - if (p == null) { - // default to http - p = s.getAttribute(Attributes.HTTP_PORT); - } - - if (groovyTruth(h) && p != null) return h+":"+p; - log.error("Unable to construct hostname:port representation for "+s+"; skipping in "+this); - return null; - } - - protected synchronized void recomputeAddresses() { - Set<String> resultM = Sets.newLinkedHashSet(); - for (Entity s: getMembers()) { - String hp = getAddressOfEntity(s); - if (hp != null) resultM.add(hp); - } - Set<String> result = Collections.unmodifiableSet(resultM); - Collection<String> oldAddresses = getAttribute(TARGET_ADDRESSES); - if (oldAddresses == null || !result.equals(ImmutableSet.copyOf(oldAddresses))) { - setAttribute(TARGET_ADDRESSES, result); - } - } - - public Collection<String> getTargetAddresses() { - return getAttribute(TARGET_ADDRESSES); - } - - public ProxySslConfig getSsl() { - return getConfig(SSL_CONFIG); - } - - // FIXME Do we really need this?! - protected SubscriptionHandle getSubscriptionHandle() { - return subscriptionHandle; - } - - private SubscriptionHandle subscriptionHandle; - private SubscriptionHandle subscriptionHandle2; - - @Override - public synchronized void recompute() { - if (subscriptionHandle != null) getSubscriptionContext().unsubscribe(subscriptionHandle); - if (subscriptionHandle2 != null) getSubscriptionContext().unsubscribe(subscriptionHandle2); - - Entity t = getTarget(); - if (t != null) { - subscriptionHandle = subscribeToChildren(t, Startable.SERVICE_UP, new SensorEventListener<Boolean>() { - @Override public void onEvent(SensorEvent<Boolean> event) { - boolean changed = (event.getValue()) ? addMember(event.getSource()) : removeMember(event.getSource()); - if (changed) { - recomputeAddresses(); - } - }}); - subscriptionHandle2 = subscribe(t, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() { - @Override public void onEvent(SensorEvent<Entity> event) { - removeMember(event.getValue()); - // recompute, irrespective of change, because framework may have already invoked the removeMember call - recomputeAddresses(); - }}); - setMembers(t.getChildren(), EntityPredicates.attributeEqualTo(Startable.SERVICE_UP, true)); - } - - recomputeAddresses(); - } - - @Override - public void discard() { - Entities.unmanage(this); - } - - private boolean hasProtocol(Collection<String> protocols, String desired) { - for (String contender : protocols) { - if ("https".equals(contender.toLowerCase())) return true; - } - return false; - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlRewriteRule.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlRewriteRule.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlRewriteRule.java deleted file mode 100644 index ee690b5..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlRewriteRule.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 brooklyn.entity.proxy.nginx; - -import java.io.Serializable; - -/** records a rewrite rule for use in URL rewriting such as by nginx; - * from and to are expected to be usual regex replacement strings, - * with the convention here (for portability) that: - * <li> - * <it> from should match the entire path (internally is wrapped with ^ and $ for nginx); - * <it> to can refer to $1, $2 from the groups in from - * </li> - * so eg use from = (.*)A(.*) and to = $1B$2 to change all occurrences of A to B - */ -public class UrlRewriteRule implements Serializable { - - private static final long serialVersionUID = -8457441487467968553L; - - String from, to; - boolean isBreak; - - /* there is also a flag "last" possible on nginx which might be useful, - * but i don't know how portable that is -- - * we'll know e.g. when we support HA Proxy and others. - * presumably everything has at least one "break-after-this-rewrite" mode - * so i think we're safe having one in here. - */ - - public UrlRewriteRule() {} - public UrlRewriteRule(String from, String to) { - this.from = from; - this.to = to; - } - - public String getFrom() { - return from; - } - public void setFrom(String from) { - this.from = from; - } - public String getTo() { - return to; - } - public void setTo(String to) { - this.to = to; - } - - public boolean isBreak() { - return isBreak; - } - public void setBreak(boolean isBreak) { - this.isBreak = isBreak; - } - - public UrlRewriteRule setBreak() { setBreak(true); return this; } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java b/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java deleted file mode 100644 index 7da9e11..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/ControlledDynamicWebAppCluster.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 brooklyn.entity.webapp; - -import org.apache.brooklyn.catalog.Catalog; -import brooklyn.config.ConfigKey; -import brooklyn.entity.Entity; -import brooklyn.entity.Group; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.ConfigurableEntityFactory; -import brooklyn.entity.basic.DynamicGroup; -import brooklyn.entity.basic.Lifecycle; -import brooklyn.entity.group.Cluster; -import brooklyn.entity.group.DynamicCluster; -import brooklyn.entity.proxy.LoadBalancer; -import brooklyn.entity.proxying.EntitySpec; -import brooklyn.entity.proxying.ImplementedBy; -import brooklyn.entity.trait.MemberReplaceable; -import brooklyn.entity.trait.Resizable; -import brooklyn.entity.trait.Startable; -import brooklyn.event.AttributeSensor; -import brooklyn.event.basic.BasicAttributeSensor; -import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; -import brooklyn.util.flags.SetFromFlag; - -/** - * This entity contains the sub-groups and entities that go in to a single location (e.g. datacenter) - * to provide web-app cluster functionality, viz load-balancer (controller) and webapp software processes. - * <p> - * You can customise the web server by customising the memberSpec. - * <p> - * The children of this entity are: - * <ul> - * <li>a {@link brooklyn.entity.group.DynamicCluster} of {@link WebAppService}s (defaults to JBoss7Server) - * <li>a cluster controller (defaulting to Nginx if none supplied) - * </ul> - * - * This entity is also a group whose members mirror those of the child DynamicCluster (so do not include the load balancer). - * This is convenient for associating policies such as ServiceReplacer with this entity, rather - * than with the child {@link brooklyn.entity.group.DynamicCluster}. However, note that changing this entity's - * members has no effect on the members of the underlying DynamicCluster - treat this as a read-only view. - */ -@Catalog(name="Controlled Dynamic Web-app Cluster", description="A cluster of load-balanced web-apps, which can be dynamically re-sized") -@ImplementedBy(ControlledDynamicWebAppClusterImpl.class) -public interface ControlledDynamicWebAppCluster extends DynamicGroup, Entity, Startable, Resizable, MemberReplaceable, - Group, ElasticJavaWebAppService, JavaWebAppService.CanDeployAndUndeploy, JavaWebAppService.CanRedeployAll { - - @SetFromFlag("initialSize") - public static ConfigKey<Integer> INITIAL_SIZE = ConfigKeys.newConfigKeyWithDefault(Cluster.INITIAL_SIZE, 1); - - @SetFromFlag("controller") - public static BasicAttributeSensorAndConfigKey<LoadBalancer> CONTROLLER = new BasicAttributeSensorAndConfigKey<LoadBalancer>( - LoadBalancer.class, "controlleddynamicwebappcluster.controller", "Controller for the cluster; if null a default will created (using controllerSpec)"); - - @SetFromFlag("controlledGroup") - public static BasicAttributeSensorAndConfigKey<Group> CONTROLLED_GROUP = new BasicAttributeSensorAndConfigKey<Group>( - Group.class, "controlleddynamicwebappcluster.controlledgroup", "The group of web servers that the controller should point at; if null, will use the CLUSTER"); - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @SetFromFlag("controllerSpec") - public static BasicAttributeSensorAndConfigKey<EntitySpec<? extends LoadBalancer>> CONTROLLER_SPEC = new BasicAttributeSensorAndConfigKey( - EntitySpec.class, "controlleddynamicwebappcluster.controllerSpec", "Spec for creating the controller (if one not supplied explicitly); if null an NGINX instance will be created"); - - @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) - /** factory (or closure) to create the web server, given flags */ - @SetFromFlag("factory") - public static BasicAttributeSensorAndConfigKey<ConfigurableEntityFactory<? extends WebAppService>> FACTORY = new BasicAttributeSensorAndConfigKey( - ConfigurableEntityFactory.class, DynamicCluster.FACTORY.getName(), "factory (or closure) to create the web server"); - - @SuppressWarnings({ "unchecked", "rawtypes" }) - /** Spec for web server entiites to be created */ - @SetFromFlag("memberSpec") - public static BasicAttributeSensorAndConfigKey<EntitySpec<? extends WebAppService>> MEMBER_SPEC = new BasicAttributeSensorAndConfigKey( - EntitySpec.class, DynamicCluster.MEMBER_SPEC.getName(), "Spec for web server entiites to be created"); - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @SetFromFlag("webClusterSpec") - public static BasicAttributeSensorAndConfigKey<EntitySpec<? extends DynamicWebAppCluster>> WEB_CLUSTER_SPEC = new BasicAttributeSensorAndConfigKey( - EntitySpec.class, "controlleddynamicwebappcluster.webClusterSpec", "Spec for creating the cluster; if null a DynamicWebAppCluster will be created"); - - public static AttributeSensor<DynamicWebAppCluster> CLUSTER = new BasicAttributeSensor<DynamicWebAppCluster>( - DynamicWebAppCluster.class, "controlleddynamicwebappcluster.cluster", "Underlying web-app cluster"); - - public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME; - - public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; - - - public LoadBalancer getController(); - - public ConfigurableEntityFactory<WebAppService> getFactory(); - - public DynamicWebAppCluster getCluster(); - - public Group getControlledGroup(); -}
