http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java deleted file mode 100644 index 3beee70..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java +++ /dev/null @@ -1,185 +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.nodejs; - -import java.net.URI; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.SoftwareProcess; -import brooklyn.entity.webapp.WebAppService; -import brooklyn.location.basic.SshMachineLocation; -import brooklyn.util.collections.MutableList; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.file.ArchiveUtils; -import brooklyn.util.net.Networking; -import brooklyn.util.os.Os; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.text.Strings; - -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; - -public class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver { - - private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class); - - public NodeJsWebAppSshDriver(NodeJsWebAppServiceImpl entity, SshMachineLocation machine) { - super(entity, machine); - } - - public NodeJsWebAppServiceImpl getEntity() { - return (NodeJsWebAppServiceImpl) super.getEntity(); - } - - @Override - public Integer getHttpPort() { - return getEntity().getAttribute(Attributes.HTTP_PORT); - } - - @Override - public String getAppDir() { - return Os.mergePaths(getRunDir(), getEntity().getConfig(NodeJsWebAppService.APP_NAME)); - } - - @Override - public void postLaunch() { - String rootUrl = String.format("http://%s:%d/", getHostname(), getHttpPort()); - entity.setAttribute(Attributes.MAIN_URI, URI.create(rootUrl)); - entity.setAttribute(WebAppService.ROOT_URL, rootUrl); - } - - protected Map<String, Integer> getPortMap() { - return MutableMap.of("http", getHttpPort()); - } - - @Override - public Set<Integer> getPortsUsed() { - return ImmutableSet.<Integer>builder() - .addAll(super.getPortsUsed()) - .addAll(getPortMap().values()) - .build(); - } - - // TODO Suggest that other entities follow this pattern as well: check for port availability early - // to report failures early, and in case getShellEnvironment() tries to convert any null port numbers - // to int. - @Override - public void preInstall() { - super.preInstall(); - Networking.checkPortsValid(getPortMap()); - } - - @Override - public void install() { - LOG.info("Installing Node.JS {}", getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION)); - - List<String> commands = MutableList.<String>builder() - .add(BashCommands.INSTALL_CURL) - .add(BashCommands.ifExecutableElse0("apt-get", BashCommands.chain( - BashCommands.installPackage("software-properties-common python-software-properties python g++ make"), - BashCommands.sudo("add-apt-repository ppa:chris-lea/node.js")))) - .add(BashCommands.installPackage(MutableMap.of("yum", "git nodejs npm", "apt", "git-core nodejs"), null)) - .add("mkdir \"$HOME/.npm\"") - .add(BashCommands.sudo("npm install -g n")) - .add(BashCommands.sudo("n " + getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION))) - .build(); - - newScript(INSTALLING) - .body.append(commands) - .execute(); - } - - @Override - public void customize() { - List<String> commands = Lists.newLinkedList(); - - String gitRepoUrl = getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL); - String archiveUrl = getEntity().getConfig(NodeJsWebAppService.APP_ARCHIVE_URL); - String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); - if (Strings.isNonBlank(gitRepoUrl) && Strings.isNonBlank(archiveUrl)) { - throw new IllegalStateException("Only one of Git or archive URL must be set for " + getEntity()); - } else if (Strings.isNonBlank(gitRepoUrl)) { - commands.add(String.format("git clone %s %s", gitRepoUrl, appName)); - commands.add(String.format("cd %s", appName)); - } else if (Strings.isNonBlank(archiveUrl)) { - ArchiveUtils.deploy(archiveUrl, getMachine(), getRunDir()); - } else { - throw new IllegalStateException("At least one of Git or archive URL must be set for " + getEntity()); - } - - commands.add(BashCommands.ifFileExistsElse1("package.json", "npm install")); - List<String> packages = getEntity().getConfig(NodeJsWebAppService.NODE_PACKAGE_LIST); - if (packages != null && packages.size() > 0) { - commands.add(BashCommands.sudo("npm install -g " + Joiner.on(' ').join(packages))); - } - - newScript(CUSTOMIZING) - .body.append(commands) - .execute(); - } - - @Override - public void launch() { - List<String> commands = Lists.newLinkedList(); - - String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); - String appFile = getEntity().getConfig(NodeJsWebAppService.APP_FILE); - String appCommand = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND); - String appCommandLine = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND_LINE); - - if (Strings.isBlank(appCommandLine)) { - appCommandLine = appCommand + " " + appFile; - } - - // Ensure global NPM modules are on Node's path. - commands.add("export NODE_PATH=\"$NODE_PATH:$(npm root -g)\""); - commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))); - commands.add("nohup " + appCommandLine + " > console.out 2>&1 &"); - - newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING) - .body.append(commands) - .execute(); - } - - @Override - public boolean isRunning() { - return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0; - } - - @Override - public void stop() { - newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute(); - } - - @Override - public Map<String, String> getShellEnvironment() { - return MutableMap.<String, String>builder().putAll(super.getShellEnvironment()) - .put("PORT", Integer.toString(getHttpPort())) - .build(); - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java deleted file mode 100644 index d5a98ac..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java +++ /dev/null @@ -1,23 +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.tomcat; - -@Deprecated -public interface Tomcat7Driver extends TomcatDriver { -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java deleted file mode 100644 index 7fc6150..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java +++ /dev/null @@ -1,29 +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.tomcat; - -import brooklyn.location.basic.SshMachineLocation; - -@Deprecated -public class Tomcat7SshDriver extends TomcatSshDriver implements Tomcat7Driver { - - public Tomcat7SshDriver(TomcatServerImpl entity, SshMachineLocation machine) { - super(entity, machine); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java deleted file mode 100644 index 6262ac7..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java +++ /dev/null @@ -1,55 +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.tomcat; - -import org.apache.brooklyn.catalog.Catalog; -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.SoftwareProcess; -import brooklyn.entity.proxying.ImplementedBy; -import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; -import brooklyn.util.flags.SetFromFlag; -import brooklyn.util.javalang.JavaClassNames; - -/** - * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance. - */ -@Catalog(name="Tomcat Server", - description="Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies", - iconUrl="classpath:///tomcat-logo.png") -@ImplementedBy(Tomcat8ServerImpl.class) -public interface Tomcat8Server extends TomcatServer { - - @SetFromFlag("version") - ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "8.0.22"); - - @SetFromFlag("downloadUrl") - BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( - SoftwareProcess.DOWNLOAD_URL, "http://download.nextag.com/apache/tomcat/tomcat-8/v${version}/bin/apache-tomcat-${version}.tar.gz"); - - @SetFromFlag("server.xml") - ConfigKey<String> SERVER_XML_RESOURCE = ConfigKeys.newStringConfigKey( - "tomcat.serverxml", "The file to template and use as the Tomcat process' server.xml", - JavaClassNames.resolveClasspathUrl(Tomcat8Server.class, "tomcat8-server.xml")); - - @SetFromFlag("web.xml") - ConfigKey<String> WEB_XML_RESOURCE = ConfigKeys.newStringConfigKey( - "tomcat.webxml", "The file to template and use as the Tomcat process' web.xml", - JavaClassNames.resolveClasspathUrl(Tomcat8Server.class, "tomcat8-web.xml")); -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java deleted file mode 100644 index 6858f51..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java +++ /dev/null @@ -1,26 +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.tomcat; - -/** - * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance. - */ -public class Tomcat8ServerImpl extends TomcatServerImpl implements Tomcat8Server { -} - http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java deleted file mode 100644 index 00b4628..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java +++ /dev/null @@ -1,24 +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.tomcat; - -import brooklyn.entity.webapp.JavaWebAppDriver; - -public interface TomcatDriver extends JavaWebAppDriver { -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java deleted file mode 100644 index 24cd729..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java +++ /dev/null @@ -1,80 +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.tomcat; - -import org.apache.brooklyn.catalog.Catalog; -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.SoftwareProcess; -import brooklyn.entity.java.UsesJmx; -import brooklyn.entity.proxying.ImplementedBy; -import brooklyn.entity.trait.HasShortName; -import brooklyn.entity.webapp.JavaWebAppSoftwareProcess; -import brooklyn.event.AttributeSensor; -import brooklyn.event.basic.BasicAttributeSensor; -import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; -import brooklyn.event.basic.PortAttributeSensorAndConfigKey; -import brooklyn.location.basic.PortRanges; -import brooklyn.util.flags.SetFromFlag; -import brooklyn.util.javalang.JavaClassNames; -import brooklyn.util.time.Duration; - -/** - * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance. - */ -@Catalog(name="Tomcat Server", - description="Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies", - iconUrl="classpath:///tomcat-logo.png") -@ImplementedBy(TomcatServerImpl.class) -public interface TomcatServer extends JavaWebAppSoftwareProcess, UsesJmx, HasShortName { - - @SetFromFlag("version") - ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "7.0.56"); - - @SetFromFlag("downloadUrl") - BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( - SoftwareProcess.DOWNLOAD_URL, "http://download.nextag.com/apache/tomcat/tomcat-7/v${version}/bin/apache-tomcat-${version}.tar.gz"); - - /** - * Tomcat insists on having a port you can connect to for the sole purpose of shutting it down. - * Don't see an easy way to disable it; causes collisions in its out-of-the-box location of 8005, - * so override default here to a high-numbered port. - */ - @SetFromFlag("shutdownPort") - PortAttributeSensorAndConfigKey SHUTDOWN_PORT = - ConfigKeys.newPortSensorAndConfigKey("tomcat.shutdownport", "Suggested shutdown port", PortRanges.fromString("31880+")); - - @SetFromFlag("server.xml") - ConfigKey<String> SERVER_XML_RESOURCE = ConfigKeys.newStringConfigKey( - "tomcat.serverxml", "The file to template and use as the Tomcat process' server.xml", - JavaClassNames.resolveClasspathUrl(TomcatServer.class, "server.xml")); - - @SetFromFlag("web.xml") - ConfigKey<String> WEB_XML_RESOURCE = ConfigKeys.newStringConfigKey( - "tomcat.webxml", "The file to template and use as the Tomcat process' web.xml", - JavaClassNames.resolveClasspathUrl(TomcatServer.class, "web.xml")); - - ConfigKey<Duration> START_TIMEOUT = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.START_TIMEOUT, Duration.FIVE_MINUTES); - - AttributeSensor<String> CONNECTOR_STATUS = - new BasicAttributeSensor<String>(String.class, "webapp.tomcat.connectorStatus", "Catalina connector state name"); - - AttributeSensor<String> JMX_SERVICE_URL = UsesJmx.JMX_URL; - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java deleted file mode 100644 index e5b5ac0..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java +++ /dev/null @@ -1,120 +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.tomcat; - -import static java.lang.String.format; - -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.java.JavaAppUtils; -import brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; -import brooklyn.event.feed.jmx.JmxAttributePollConfig; -import brooklyn.event.feed.jmx.JmxFeed; - -import com.google.common.base.Functions; -import com.google.common.base.Predicates; - -/** - * An {@link brooklyn.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)) - .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"; - } -} - http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java deleted file mode 100644 index 76c820d..0000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java +++ /dev/null @@ -1,174 +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.tomcat; - -import static java.lang.String.format; - -import java.io.InputStream; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import brooklyn.entity.basic.Entities; -import brooklyn.entity.webapp.JavaWebAppSshDriver; -import brooklyn.location.basic.SshMachineLocation; -import brooklyn.util.collections.MutableList; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.net.Networking; -import brooklyn.util.os.Os; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.text.StringEscapes.BashStringEscapes; - -import com.google.common.base.Preconditions; - -public class TomcatSshDriver extends JavaWebAppSshDriver implements TomcatDriver { - - private static final String KEYSTORE_FILE = "keystore"; - - public TomcatSshDriver(TomcatServerImpl entity, SshMachineLocation machine) { - super(entity, machine); - } - - @Override - public void preInstall() { - resolver = Entities.newDownloader(this); - setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName("apache-tomcat-"+getVersion()))); - } - - @Override - public void install() { - List<String> urls = resolver.getTargets(); - String saveAs = resolver.getFilename(); - - List<String> commands = new LinkedList<String>(); - commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); - commands.add(BashCommands.INSTALL_TAR); - commands.add(format("tar xvzf %s", saveAs)); - - newScript(INSTALLING) - .environmentVariablesReset() - .body.append(commands) - .execute(); - } - - @Override - public void customize() { - newScript(CUSTOMIZING) - .body.append("mkdir -p conf logs webapps temp") - .failOnNonZeroResultCode() - .execute(); - - copyTemplate(entity.getConfig(TomcatServer.SERVER_XML_RESOURCE), Os.mergePaths(getRunDir(), "conf", "server.xml")); - copyTemplate(entity.getConfig(TomcatServer.WEB_XML_RESOURCE), Os.mergePaths(getRunDir(), "conf", "web.xml")); - - // Deduplicate same code in JBoss - if (isProtocolEnabled("HTTPS")) { - String keystoreUrl = Preconditions.checkNotNull(getSslKeystoreUrl(), "keystore URL must be specified if using HTTPS for " + entity); - String destinationSslKeystoreFile = getHttpsSslKeystoreFile(); - InputStream keystoreStream = resource.getResourceFromUrl(keystoreUrl); - getMachine().copyTo(keystoreStream, destinationSslKeystoreFile); - } - - getEntity().deployInitialWars(); - } - - @Override - public void launch() { - Map<String, Integer> ports = MutableMap.of("httpPort", getHttpPort(), "shutdownPort", getShutdownPort()); - Networking.checkPortsValid(ports); - - // We wait for evidence of tomcat 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(USE_PID_FILE, false), LAUNCHING) - .body.append( - format("%s/bin/startup.sh >>$RUN/console 2>&1 </dev/null",getExpandedInstallDir()), - "for i in {1..10}\n" + - "do\n" + - " if [ -s "+getLogFileLocation()+" ]; then exit; fi\n" + - " sleep 1\n" + - "done\n" + - "echo \"Couldn't determine if tomcat-server is running (logs/catalina.out is still empty); continuing but may subsequently fail\"" - ) - .execute(); - } - - @Override - public boolean isRunning() { - return newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), CHECK_RUNNING).execute() == 0; - } - - @Override - public void stop() { - newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), STOPPING).execute(); - } - - @Override - public void kill() { - newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), KILLING).execute(); - } - - @Override - protected List<String> getCustomJavaConfigOptions() { - return MutableList.<String>builder() - .addAll(super.getCustomJavaConfigOptions()) - .add("-Xms200m") - .add("-Xmx800m") - .add("-XX:MaxPermSize=400m") - .build(); - } - - @Override - public Map<String, String> getShellEnvironment() { - Map<String, String> shellEnv = MutableMap.<String, String>builder() - .putAll(super.getShellEnvironment()) - .remove("JAVA_OPTS") - .put("CATALINA_PID", "pid.txt") - .put("CATALINA_BASE", getRunDir()) - .put("RUN", getRunDir()) - .build(); - - // Double quoting of individual JAVA_OPTS entries required due to eval in catalina.sh - List<String> javaOpts = getJavaOpts(); - String sJavaOpts = BashStringEscapes.doubleQuoteLiteralsForBash(javaOpts.toArray(new String[0])); - shellEnv.put("CATALINA_OPTS", sJavaOpts); - - return shellEnv; - } - - @Override - protected String getLogFileLocation() { - return Os.mergePathsUnix(getRunDir(), "logs/catalina.out"); - } - - @Override - protected String getDeploySubdir() { - return "webapps"; - } - - public Integer getShutdownPort() { - return entity.getAttribute(TomcatServerImpl.SHUTDOWN_PORT); - } - - public String getHttpsSslKeystoreFile() { - return Os.mergePathsUnix(getRunDir(), "conf", KEYSTORE_FILE); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java new file mode 100644 index 0000000..670a6aa --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.Map; + +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.Lifecycle; +import brooklyn.entity.trait.Startable; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.location.geo.HostGeoInfo; + +import com.google.common.reflect.TypeToken; + +public interface AbstractGeoDnsService extends Entity { + + public static final ConfigKey<Boolean> INCLUDE_HOMELESS_ENTITIES = ConfigKeys.newBooleanConfigKey("geodns.includeHomeless", "Whether to include entities whose geo-coordinates cannot be inferred", false); + public static final ConfigKey<Boolean> USE_HOSTNAMES = ConfigKeys.newBooleanConfigKey("geodns.useHostnames", "Whether to use the hostname for the returned value for routing, rather than IP address (defaults to true)", true); + + public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; + public static final AttributeSensor<Boolean> SERVICE_UP = Startable.SERVICE_UP; + public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME; + public static final AttributeSensor<String> ADDRESS = Attributes.ADDRESS; + @SuppressWarnings("serial") + public static final AttributeSensor<Map<String,String>> TARGETS = new BasicAttributeSensor<Map<String,String>>( + new TypeToken<Map<String,String>>() {}, "geodns.targets", "Map of targets currently being managed (entity ID to URL)"); + + public void setServiceState(Lifecycle state); + + /** sets target to be a group whose *members* will be searched (non-Group items not supported) */ + // prior to 0.7.0 the API accepted non-group items, but did not handle them correctly + public void setTargetEntityProvider(final Group entityProvider); + + /** should return the hostname which this DNS service is configuring */ + public String getHostname(); + + public Map<Entity, HostGeoInfo> getTargetHosts(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java new file mode 100644 index 0000000..a6b2b5a --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java @@ -0,0 +1,373 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.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.entity.webapp.WebAppService; +import org.apache.brooklyn.policy.PolicySpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.Entity; +import brooklyn.entity.Group; +import brooklyn.entity.basic.AbstractEntity; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.DynamicGroup; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.basic.ServiceStateLogic; +import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic; +import brooklyn.entity.group.AbstractMembershipTrackingPolicy; +import brooklyn.location.geo.HostGeoInfo; +import brooklyn.util.collections.MutableSet; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.net.Networking; +import brooklyn.util.time.Duration; +import 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 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) { + setAttribute(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(); + log.debug("Initializing tracker for "+this+", following "+targetEntityProvider); + tracker = addPolicy(PolicySpec.create(MemberTrackingPolicy.class) + .displayName("GeoDNS targets tracker") + .configure("sensorsToTrack", ImmutableSet.of(HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL)) + .configure("group", targetEntityProvider)); + refreshGroupMembership(); + } + + protected synchronized void endTracker() { + if (tracker == null || targetEntityProvider==null) return; + removePolicy(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; + Set<Entity> previousOnes = MutableSet.copyOf(targetHosts.keySet()); + for (Entity e: pool) { + previousOnes.remove(e); + changed |= addTargetHost(e); + } + // anything left in previousOnes is no longer applicable + for (Entity e: previousOnes) { + changed = true; + 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))) + 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); + setAttribute(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/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java new file mode 100644 index 0000000..31c8831 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java @@ -0,0 +1,71 @@ +/* + * 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.entity.dns.AbstractGeoDnsService; +import org.apache.brooklyn.entity.webapp.WebAppServiceConstants; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.util.flags.SetFromFlag; + +@ImplementedBy(GeoscalingDnsServiceImpl.class) +public interface GeoscalingDnsService extends AbstractGeoDnsService { + + @SetFromFlag("sslTrustAll") + public static final 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"); + @SetFromFlag("username") + public static final ConfigKey<String> GEOSCALING_USERNAME = new BasicConfigKey<String>( + String.class, "geoscaling.username"); + @SetFromFlag("password") + public static final ConfigKey<String> GEOSCALING_PASSWORD = new BasicConfigKey<String>( + String.class, "geoscaling.password"); + @SetFromFlag("primaryDomainName") + public static final ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = new BasicConfigKey<String>( + String.class, "geoscaling.primary.domain.name"); + @SetFromFlag("smartSubdomainName") + public static final ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = new BasicConfigKey<String>( + String.class, "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; " + + "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(); + + /** minimum/default TTL here is 300s = 5m */ + public long getTimeToLiveSeconds(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java new file mode 100644 index 0000000..5779ec2 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java @@ -0,0 +1,200 @@ +/* + * 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.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 brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.basic.ServiceStateLogic; +import brooklyn.location.geo.HostGeoInfo; +import brooklyn.util.collections.MutableSet; +import brooklyn.util.http.HttpTool; +import brooklyn.util.text.Identifiers; +import 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) setConfig(RANDOMIZE_SUBDOMAIN_NAME, true); + + Boolean trustAll = getConfig(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); + + // 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"); + setAttribute(GEOSCALING_ACCOUNT, username); + setAttribute(MANAGED_DOMAIN, fullDomain); + setAttribute(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); + setAttribute(ROOT_URL, null); + setAttribute(MAIN_URI, null); + } else { + setServiceState(Lifecycle.RUNNING); + String domain = getAttribute(MANAGED_DOMAIN); + if (!Strings.isEmpty(domain)) { + setAttribute(ROOT_URL, "http://"+domain+"/"); + setAttribute(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/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java new file mode 100644 index 0000000..ae3883f --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java @@ -0,0 +1,79 @@ +/* + * 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.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.TimeZone; + +import brooklyn.location.geo.HostGeoInfo; +import brooklyn.util.ResourceUtils; +import brooklyn.util.javalang.JavaClassNames; +import brooklyn.util.os.Os; +import brooklyn.util.text.Strings; + +public class GeoscalingScriptGenerator { + + private static final String PHP_SCRIPT_TEMPLATE_RESOURCE = JavaClassNames.resolveClasspathUrl(GeoscalingScriptGenerator.class, "template.php"); + private static final String HOSTS_DECLARATIONS_MARKER = "/* HOST DECLARATIONS TO BE SUBSTITUTED HERE */"; + private static final String DATESTAMP_MARKER = "DATESTAMP"; + + + public static String generateScriptString(Collection<HostGeoInfo> hosts) { + return generateScriptString(new Date(), hosts); + } + + public static String generateScriptString(Date generationTime, Collection<HostGeoInfo> hosts) { + String template = ResourceUtils.create(GeoscalingScriptGenerator.class).getResourceAsString(PHP_SCRIPT_TEMPLATE_RESOURCE); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String datestamp = sdf.format(generationTime); + String declarations = getHostsDeclaration(hosts); + return template + .replaceAll(DATESTAMP_MARKER, datestamp) + .replace(HOSTS_DECLARATIONS_MARKER, declarations); + } + + private static String getHostsDeclaration(Collection<HostGeoInfo> hosts) { + StringBuffer sb = new StringBuffer(); + sb.append("$hosts = array(").append(Os.LINE_SEPARATOR); + Iterator<HostGeoInfo> iServer = hosts.iterator(); + while (iServer.hasNext()) { + HostGeoInfo server = iServer.next(); + sb.append(" array('name' => '").append(escape(server.displayName)).append("',").append(Os.LINE_SEPARATOR); + sb.append(" 'latitude' => ").append(server.latitude).append(",").append(Os.LINE_SEPARATOR); + sb.append(" 'longitude' => ").append(server.longitude).append(",").append(Os.LINE_SEPARATOR); + sb.append(" 'ip' => '").append(escape(server.address)).append("')"); + if (iServer.hasNext()) sb.append(",").append(Os.LINE_SEPARATOR); + sb.append(Os.LINE_SEPARATOR); + } + sb.append(");").append(Os.LINE_SEPARATOR); + return sb.toString(); + } + + private static String escape(String txt) { + txt = Strings.replaceAllNonRegex(txt, "\\", "\\\\"); + txt = Strings.replaceAllNonRegex(txt, "'", "\\'"); + txt = Strings.replaceAllNonRegex(txt, "\"", "\\\"'"); + return txt; + } + +}
