http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayDriver.java new file mode 100644 index 0000000..c7feab6 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayDriver.java @@ -0,0 +1,27 @@ +/* + * 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.nosql.couchbase; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface CouchbaseSyncGatewayDriver extends SoftwareProcessDriver { + + public String getOsTag(); + +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayImpl.java new file mode 100644 index 0000000..14d8760 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewayImpl.java @@ -0,0 +1,82 @@ +/* + * 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.nosql.couchbase; + +import brooklyn.config.render.RendererHints; +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.location.access.BrooklynAccessUtils; + +import com.google.common.base.Functions; +import com.google.common.net.HostAndPort; + +public class CouchbaseSyncGatewayImpl extends SoftwareProcessImpl implements CouchbaseSyncGateway { + + private HttpFeed httpFeed; + + @Override + public Class<CouchbaseSyncGatewayDriver> getDriverInterface() { + return CouchbaseSyncGatewayDriver.class; + } + + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } + + @Override + protected void connectServiceUpIsRunning() { + HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, + getAttribute(CouchbaseSyncGateway.ADMIN_REST_API_PORT)); + + String managementUri = String.format("http://%s:%s", + hp.getHostText(), hp.getPort()); + + setAttribute(MANAGEMENT_URL, managementUri); + + httpFeed = HttpFeed.builder() + .entity(this) + .period(200) + .baseUri(managementUri) + .poll(new HttpPollConfig<Boolean>(SERVICE_UP) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .onFailureOrException(Functions.constant(false))) + .build(); + } + + @Override + protected void disconnectSensors() { + super.disconnectSensors(); + disconnectServiceUpIsRunning(); + } + + @Override + protected void disconnectServiceUpIsRunning() { + if (httpFeed != null) { + httpFeed.stop(); + } + } + + static { + RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewaySshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewaySshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewaySshDriver.java new file mode 100644 index 0000000..d2d18da --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchbase/CouchbaseSyncGatewaySshDriver.java @@ -0,0 +1,167 @@ +/* + * 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.nosql.couchbase; + +import static brooklyn.util.ssh.BashCommands.INSTALL_CURL; +import static brooklyn.util.ssh.BashCommands.alternatives; +import static brooklyn.util.ssh.BashCommands.chainGroup; +import static brooklyn.util.ssh.BashCommands.sudo; +import static java.lang.String.format; + +import java.util.List; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.basic.EntityPredicates; +import brooklyn.entity.drivers.downloads.DownloadResolver; +import brooklyn.event.basic.DependentConfiguration; +import brooklyn.location.OsDetails; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; + +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class CouchbaseSyncGatewaySshDriver extends AbstractSoftwareProcessSshDriver implements CouchbaseSyncGatewayDriver { + public CouchbaseSyncGatewaySshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public void stop() { + + } + + @Override + public void install() { + //reference http://docs.couchbase.com/sync-gateway/#getting-started-with-sync-gateway + DownloadResolver resolver = Entities.newDownloader(this); + List<String> urls = resolver.getTargets(); + String saveAs = resolver.getFilename(); + + OsDetails osDetails = getMachine().getMachineDetails().getOsDetails(); + + log.info("Installing couchbase-sync-gateway version: {}", getVersion()); + if (osDetails.isLinux()) { + List<String> commands = installLinux(urls, saveAs); + newScript(INSTALLING) + .body.append(commands).execute(); + } + } + + @Override + public void customize() { + + } + + @Override + public void launch() { + Entity cbNode = entity.getConfig(CouchbaseSyncGateway.COUCHBASE_SERVER); + Entities.waitForServiceUp(cbNode, Duration.ONE_HOUR); + DependentConfiguration.waitInTaskForAttributeReady(cbNode, CouchbaseCluster.IS_CLUSTER_INITIALIZED, Predicates.equalTo(true)); + // Even once the bucket has published its API URL, it can still take a couple of seconds for it to become available + Time.sleep(10 * 1000); + if (cbNode instanceof CouchbaseCluster) { + // in_cluster now applies even to a node in a cluster of size 1 + Optional<Entity> cbClusterNode = Iterables.tryFind(cbNode.getAttribute(CouchbaseCluster.GROUP_MEMBERS), + Predicates.and(Predicates.instanceOf(CouchbaseNode.class), EntityPredicates.attributeEqualTo(CouchbaseNode.IS_IN_CLUSTER, Boolean.TRUE))); + + if (!cbClusterNode.isPresent()) { + throw new IllegalArgumentException(format("The cluster %s does not contain any suitable Couchbase nodes to connect to..", cbNode.getId())); + } + + cbNode = cbClusterNode.get(); + } + String hostname = cbNode.getAttribute(CouchbaseNode.HOSTNAME); + String webPort = cbNode.getAttribute(CouchbaseNode.COUCHBASE_WEB_ADMIN_PORT).toString(); + + + String username = cbNode.getConfig(CouchbaseNode.COUCHBASE_ADMIN_USERNAME); + String password = cbNode.getConfig(CouchbaseNode.COUCHBASE_ADMIN_PASSWORD); + + String bucketName = entity.getConfig(CouchbaseSyncGateway.COUCHBASE_SERVER_BUCKET); + String pool = entity.getConfig(CouchbaseSyncGateway.COUCHBASE_SERVER_POOL); + String pretty = entity.getConfig(CouchbaseSyncGateway.PRETTY) ? "-pretty" : ""; + String verbose = entity.getConfig(CouchbaseSyncGateway.VERBOSE) ? "-verbose" : ""; + + String adminRestApiPort = entity.getConfig(CouchbaseSyncGateway.ADMIN_REST_API_PORT).iterator().next().toString(); + String syncRestApiPort = entity.getConfig(CouchbaseSyncGateway.SYNC_REST_API_PORT).iterator().next().toString(); + + String serverWebAdminUrl = format("http://%s:%s@%s:%s", username, password, hostname, webPort); + String options = format("-url %s -bucket %s -adminInterface 0.0.0.0:%s -interface 0.0.0.0:%s -pool %s %s %s", + serverWebAdminUrl, bucketName, adminRestApiPort, syncRestApiPort, pool, pretty, verbose); + + newScript(ImmutableMap.of("usePidFile", true), LAUNCHING) + .body.append(format("/opt/couchbase-sync-gateway/bin/sync_gateway %s ", options) + "> out.log 2> err.log < /dev/null &") + .failOnNonZeroResultCode() + .execute(); + } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of("usePidFile", true), CHECK_RUNNING).execute() == 0; + } + + @Override + public void kill() { + newScript(MutableMap.of("usePidFile", true), KILLING).execute(); + } + + private List<String> installLinux(List<String> urls, String saveAs) { + + String apt = chainGroup( + "which apt-get", + sudo("apt-get update"), + sudo(format("dpkg -i %s", saveAs))); + + String yum = chainGroup( + "which yum", + sudo(format("rpm --install %s", saveAs))); + + return ImmutableList.<String>builder() + .add(INSTALL_CURL) + .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) + .add(alternatives(apt, yum)) + .build(); + } + + @Override + public String getOsTag() { + OsDetails os = getLocation().getOsDetails(); + if (os == null) { + // Default to generic linux + return "x86_64.rpm"; + } else { + //FIXME should be a better way to check for OS name and version + String osName = os.getName().toLowerCase(); + String fileExtension = osName.contains("deb") || osName.contains("ubuntu") ? ".deb" : ".rpm"; + String arch = os.is64bit() ? "x86_64" : "x86"; + return arch + fileExtension; + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBCluster.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBCluster.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBCluster.java new file mode 100644 index 0000000..852a4a4 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBCluster.java @@ -0,0 +1,48 @@ +/* + * 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.nosql.couchdb; + +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.event.basic.Sensors; +import brooklyn.util.flags.SetFromFlag; + +/** + * A cluster of {@link CouchDBNode}s based on {@link DynamicCluster} which can be resized by a policy if required. + * + * TODO add sensors with aggregated CouchDB statistics from cluster + */ +@ImplementedBy(CouchDBClusterImpl.class) +public interface CouchDBCluster extends DynamicCluster { + + @SetFromFlag("clusterName") + BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = new BasicAttributeSensorAndConfigKey<String>(String.class, "couchdb.cluster.name", "Name of the CouchDB cluster", "BrooklynCluster"); + + AttributeSensor<String> HOSTNAME = Sensors.newStringSensor("couchdb.cluster.hostname", "Hostname to connect to cluster with"); + + AttributeSensor<Integer> HTTP_PORT = Sensors.newIntegerSensor("couchdb.cluster.http.port", "CouchDB HTTP port to connect to cluster with"); + + /** + * The name of the cluster. + */ + String getClusterName(); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java new file mode 100644 index 0000000..7c576a1 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBClusterImpl.java @@ -0,0 +1,51 @@ +/* + * 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.nosql.couchdb; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.group.DynamicClusterImpl; +import brooklyn.entity.proxying.EntitySpec; + +/** + * Implementation of {@link CouchDBCluster}. + */ +public class CouchDBClusterImpl extends DynamicClusterImpl implements CouchDBCluster { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(CouchDBClusterImpl.class); + + public CouchDBClusterImpl() { + } + + /** + * Sets the default {@link #MEMBER_SPEC} to describe the CouchDB nodes. + */ + @Override + protected EntitySpec<?> getMemberSpec() { + return getConfig(MEMBER_SPEC, EntitySpec.create(CouchDBNode.class)); + } + + @Override + public String getClusterName() { + return getAttribute(CLUSTER_NAME); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNode.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNode.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNode.java new file mode 100644 index 0000000..be169b9 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNode.java @@ -0,0 +1,66 @@ +/* + * 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.nosql.couchdb; + +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.entity.webapp.WebAppService; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.util.flags.SetFromFlag; + +/** + * An {@link brooklyn.entity.Entity} that represents a CouchDB node in a {@link CouchDBCluster}. + */ +@Catalog(name="CouchDB Node", + description="Apache CouchDB is a database that uses JSON for documents, JavaScript for MapReduce queries, " + + "and regular HTTP for an API", + iconUrl="classpath:///couchdb-logo.png") +@ImplementedBy(CouchDBNodeImpl.class) +public interface CouchDBNode extends SoftwareProcess, WebAppService { + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "1.2.1"); + + @SetFromFlag("erlangVersion") + ConfigKey<String> ERLANG_VERSION = ConfigKeys.newStringConfigKey("erlang.version", "Erlang runtime version", "R15B"); + + @SetFromFlag("clusterName") + BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = CouchDBCluster.CLUSTER_NAME; + + @SetFromFlag("couchdbConfigTemplateUrl") + BasicAttributeSensorAndConfigKey<String> COUCHDB_CONFIG_TEMPLATE_URL = new BasicAttributeSensorAndConfigKey<String>( + String.class, "couchdb.config.templateUrl", "Template file (in freemarker format) for the couchdb config file", + "classpath://org/apache/brooklyn/entity/nosql/couchdb/couch.ini"); + + @SetFromFlag("couchdbUriTemplateUrl") + BasicAttributeSensorAndConfigKey<String> COUCHDB_URI_TEMPLATE_URL = new BasicAttributeSensorAndConfigKey<String>( + String.class, "couchdb.uri.templateUrl", "Template file (in freemarker format) for the couchdb URI file", + "classpath://org/apache/brooklyn/entity/nosql/couchdb/couch.uri"); + + @SetFromFlag("couchdbConfigFileName") + BasicAttributeSensorAndConfigKey<String> COUCHDB_CONFIG_FILE_NAME = new BasicAttributeSensorAndConfigKey<String>( + String.class, "couchdb.config.fileName", "Name for the copied config file", "local.ini"); + + Integer getHttpPort(); + + Integer getHttpsPort(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeDriver.java new file mode 100644 index 0000000..14386a0 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeDriver.java @@ -0,0 +1,37 @@ +/* + * 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.nosql.couchdb; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface CouchDBNodeDriver extends SoftwareProcessDriver { + + Integer getHttpPort(); + + Integer getHttpsPort(); + + String getClusterName(); + + String getCouchDBConfigTemplateUrl(); + + String getCouchDBUriTemplateUrl(); + + String getCouchDBConfigFileName(); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java new file mode 100644 index 0000000..298b6b6 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java @@ -0,0 +1,106 @@ +/* + * 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.nosql.couchdb; + +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; +import brooklyn.entity.webapp.WebAppServiceMethods; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +/** + * Implementation of {@link CouchDBNode}. + */ +public class CouchDBNodeImpl extends SoftwareProcessImpl implements CouchDBNode { + + private static final Logger log = LoggerFactory.getLogger(CouchDBNodeImpl.class); + + public CouchDBNodeImpl() { + } + + public Integer getHttpPort() { return getAttribute(CouchDBNode.HTTP_PORT); } + public Integer getHttpsPort() { return getAttribute(CouchDBNode.HTTPS_PORT); } + public String getClusterName() { return getAttribute(CouchDBNode.CLUSTER_NAME); } + + @Override + public Class<CouchDBNodeDriver> getDriverInterface() { + return CouchDBNodeDriver.class; + } + + private volatile HttpFeed httpFeed; + + @Override + protected void connectSensors() { + super.connectSensors(); + + connectServiceUpIsRunning(); + + httpFeed = HttpFeed.builder() + .entity(this) + .period(500, TimeUnit.MILLISECONDS) + .baseUri(String.format("http://%s:%d/_stats", getAttribute(HOSTNAME), getHttpPort())) + .poll(new HttpPollConfig<Integer>(REQUEST_COUNT) + .onSuccess(HttpValueFunctions.jsonContents(new String[] { "httpd", "requests", "count" }, Integer.class)) + .onFailureOrException(Functions.constant(-1))) + .poll(new HttpPollConfig<Integer>(ERROR_COUNT) + .onSuccess(HttpValueFunctions.jsonContents(new String[] { "httpd_status_codes", "404", "count" }, Integer.class)) + .onFailureOrException(Functions.constant(-1))) + .poll(new HttpPollConfig<Integer>(TOTAL_PROCESSING_TIME) + .onSuccess(HttpValueFunctions.jsonContents(new String[] { "couchdb", "request_time", "count" }, Integer.class)) + .onFailureOrException(Functions.constant(-1))) + .poll(new HttpPollConfig<Integer>(MAX_PROCESSING_TIME) + .onSuccess(HttpValueFunctions.chain(HttpValueFunctions.jsonContents(new String[] { "couchdb", "request_time", "max" }, Double.class), new Function<Double, Integer>() { + @Override + public Integer apply(@Nullable Double input) { + return Integer.valueOf(input.intValue()); + } + })) + .onFailureOrException(Functions.constant(-1))) + .build(); + + WebAppServiceMethods.connectWebAppServerPolicies(this); + } + + @Override + public void disconnectSensors() { + super.disconnectSensors(); + if (httpFeed != null) httpFeed.stop(); + disconnectServiceUpIsRunning(); + } + + /** @see JavaWebAppSoftwareProcessImpl#postStop() */ + @Override + protected void postStop() { + super.postStop(); + // zero our workrate derived workrates. + setAttribute(REQUESTS_PER_SECOND_LAST, 0D); + setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeSshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeSshDriver.java new file mode 100644 index 0000000..5545f08 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/couchdb/CouchDBNodeSshDriver.java @@ -0,0 +1,153 @@ +/* + * 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.nosql.couchdb; + +import static brooklyn.util.ssh.BashCommands.*; + +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.location.Location; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; + +/** + * Start a {@link CouchDBNode} in a {@link Location} accessible over ssh. + */ +public class CouchDBNodeSshDriver extends AbstractSoftwareProcessSshDriver implements CouchDBNodeDriver { + + private static final Logger log = LoggerFactory.getLogger(CouchDBNodeSshDriver.class); + + public CouchDBNodeSshDriver(CouchDBNodeImpl entity, SshMachineLocation machine) { + super(entity, machine); + + entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFileLocation()); + } + + public String getLogFileLocation() { return Os.mergePathsUnix(getRunDir(), "couchdb.log"); } + + @Override + public Integer getHttpPort() { return entity.getAttribute(CouchDBNode.HTTP_PORT); } + + @Override + public Integer getHttpsPort() { return entity.getAttribute(CouchDBNode.HTTPS_PORT); } + + @Override + public String getClusterName() { return entity.getAttribute(CouchDBNode.CLUSTER_NAME); } + + @Override + public String getCouchDBConfigTemplateUrl() { return entity.getAttribute(CouchDBNode.COUCHDB_CONFIG_TEMPLATE_URL); } + + @Override + public String getCouchDBUriTemplateUrl() { return entity.getAttribute(CouchDBNode.COUCHDB_URI_TEMPLATE_URL); } + + @Override + public String getCouchDBConfigFileName() { return entity.getAttribute(CouchDBNode.COUCHDB_CONFIG_FILE_NAME); } + + public String getErlangVersion() { return entity.getConfig(CouchDBNode.ERLANG_VERSION); } + + @Override + public void install() { + log.info("Installing {}", entity); + List<String> commands = ImmutableList.<String>builder() + .add(ifExecutableElse0("zypper", chainGroup( // SLES 11 not supported, would require building from source + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/devel:/languages:/erlang/openSUSE_11.4 erlang_suse_11")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/devel:/languages:/erlang/openSUSE_12.3 erlang_suse_12")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/devel:/languages:/erlang/openSUSE_13.1 erlang_suse_13")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/server:/database/openSUSE_11.4 db_suse_11")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/server:/database/openSUSE_12.3 db_suse_12")), + ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/server:/database/openSUSE_13.1 db_suse_13"))))) + .add(installPackage( // NOTE only 'port' states the version of Erlang used, maybe remove this constraint? + ImmutableMap.of( + "apt", "erlang-nox erlang-dev", + "port", "erlang@"+getErlangVersion()+"+ssl"), + "erlang")) + .add(installPackage("couchdb")) + .add(ifExecutableElse0("service", sudo("service couchdb stop"))) + .build(); + + newScript(INSTALLING) + .body.append(commands) + .execute(); + } + + @Override + public Set<Integer> getPortsUsed() { + Set<Integer> result = Sets.newLinkedHashSet(super.getPortsUsed()); + result.addAll(getPortMap().values()); + return result; + } + + private Map<String, Integer> getPortMap() { + return ImmutableMap.<String, Integer>builder() + .put("httpPort", getHttpPort()) + .build(); + } + + @Override + public void customize() { + log.info("Customizing {} (Cluster {})", entity, getClusterName()); + Networking.checkPortsValid(getPortMap()); + + newScript(CUSTOMIZING).execute(); + + // Copy the configuration files across + String destinationConfigFile = Os.mergePathsUnix(getRunDir(), getCouchDBConfigFileName()); + copyTemplate(getCouchDBConfigTemplateUrl(), destinationConfigFile); + String destinationUriFile = Os.mergePathsUnix(getRunDir(), "couch.uri"); + copyTemplate(getCouchDBUriTemplateUrl(), destinationUriFile); + } + + @Override + public void launch() { + log.info("Launching {}", entity); + newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING) + .body.append(sudo(String.format("nohup couchdb -p %s -a %s -o couchdb-console.log -e couchdb-error.log -b &", getPidFile(), Os.mergePathsUnix(getRunDir(), getCouchDBConfigFileName())))) + .execute(); + } + + public String getPidFile() { return Os.mergePathsUnix(getRunDir(), "couchdb.pid"); } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of(USE_PID_FILE, false), CHECK_RUNNING) + .body.append(sudo(String.format("couchdb -p %s -s", getPidFile()))) + .execute() == 0; + } + + @Override + public void stop() { + newScript(MutableMap.of(USE_PID_FILE, false), STOPPING) + .body.append(sudo(String.format("couchdb -p %s -k", getPidFile()))) + .failOnNonZeroResultCode() + .execute(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchCluster.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchCluster.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchCluster.java new file mode 100644 index 0000000..9d5bd78 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchCluster.java @@ -0,0 +1,40 @@ +/* + * 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.nosql.elasticsearch; + +import org.apache.brooklyn.catalog.Catalog; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.util.flags.SetFromFlag; + +/** + * A cluster of {@link ElasticSearchNode}s based on {@link DynamicCluster} which can be resized by a policy if required. + */ +@Catalog(name="Elastic Search Cluster", description="Elasticsearch is an open-source search server based on Lucene. " + + "It provides a distributed, multitenant-capable full-text search engine with a RESTful web interface and " + + "schema-free JSON documents.") +@ImplementedBy(ElasticSearchClusterImpl.class) +public interface ElasticSearchCluster extends DynamicCluster { + @SetFromFlag("clusterName") + BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = new BasicAttributeSensorAndConfigKey<String>(String.class, + "elasticsearch.cluster.name", "Name of the ElasticSearch cluster", "BrooklynCluster"); + + String getClusterName(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java new file mode 100644 index 0000000..27d4e9e --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchClusterImpl.java @@ -0,0 +1,45 @@ +/* + * 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.nosql.elasticsearch; + +import java.util.concurrent.atomic.AtomicInteger; + +import brooklyn.entity.group.DynamicClusterImpl; +import brooklyn.entity.proxying.EntitySpec; + +public class ElasticSearchClusterImpl extends DynamicClusterImpl implements ElasticSearchCluster { + + private AtomicInteger nextMemberId = new AtomicInteger(0); + + @Override + protected EntitySpec<?> getMemberSpec() { + EntitySpec<?> spec = EntitySpec.create(getConfig(MEMBER_SPEC, EntitySpec.create(ElasticSearchNode.class))); + + spec.configure(ElasticSearchNode.CLUSTER_NAME, getConfig(ElasticSearchClusterImpl.CLUSTER_NAME)) + .configure(ElasticSearchNode.NODE_NAME, "elasticsearch-" + nextMemberId.incrementAndGet()); + + return spec; + } + + @Override + public String getClusterName() { + return getConfig(CLUSTER_NAME); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNode.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNode.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNode.java new file mode 100644 index 0000000..34be8f1 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNode.java @@ -0,0 +1,88 @@ +/* + * 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.nosql.elasticsearch; + +import org.apache.brooklyn.catalog.Catalog; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.database.DatastoreMixins; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.entity.webapp.WebAppServiceConstants; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey; +import brooklyn.event.basic.PortAttributeSensorAndConfigKey; +import brooklyn.event.basic.Sensors; +import brooklyn.location.basic.PortRanges; +import brooklyn.util.flags.SetFromFlag; + +/** + * An {@link brooklyn.entity.Entity} that represents an ElasticSearch node + */ +@Catalog(name="Elastic Search Node", description="Elasticsearch is an open-source search server based on Lucene. " + + "It provides a distributed, multitenant-capable full-text search engine with a RESTful web interface and " + + "schema-free JSON documents.") +@ImplementedBy(ElasticSearchNodeImpl.class) +public interface ElasticSearchNode extends SoftwareProcess, DatastoreMixins.HasDatastoreUrl { + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "1.2.1"); + + @SetFromFlag("downloadUrl") + BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( + SoftwareProcess.DOWNLOAD_URL, "https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-${version}.tar.gz"); + + @SetFromFlag("dataDir") + ConfigKey<String> DATA_DIR = ConfigKeys.newStringConfigKey("elasticsearch.node.data.dir", "Directory for writing data files", null); + + @SetFromFlag("logDir") + ConfigKey<String> LOG_DIR = ConfigKeys.newStringConfigKey("elasticsearch.node.log.dir", "Directory for writing log files", null); + + @SetFromFlag("configFileUrl") + ConfigKey<String> TEMPLATE_CONFIGURATION_URL = ConfigKeys.newStringConfigKey( + "elasticsearch.node.template.configuration.url", "URL where the elasticsearch configuration file (in freemarker format) can be found", null); + + @SetFromFlag("multicastEnabled") + ConfigKey<Boolean> MULTICAST_ENABLED = ConfigKeys.newBooleanConfigKey("elasticsearch.node.multicast.enabled", + "Indicates whether zen discovery multicast should be enabled for a node", null); + + @SetFromFlag("multicastEnabled") + ConfigKey<Boolean> UNICAST_ENABLED = ConfigKeys.newBooleanConfigKey("elasticsearch.node.UNicast.enabled", + "Indicates whether zen discovery unicast should be enabled for a node", null); + + @SetFromFlag("httpPort") + PortAttributeSensorAndConfigKey HTTP_PORT = new PortAttributeSensorAndConfigKey(WebAppServiceConstants.HTTP_PORT, PortRanges.fromString("9200+")); + + @SetFromFlag("nodeName") + StringAttributeSensorAndConfigKey NODE_NAME = new StringAttributeSensorAndConfigKey("elasticsearch.node.name", + "Node name (or randomly selected if not set", null); + + @SetFromFlag("clusterName") + StringAttributeSensorAndConfigKey CLUSTER_NAME = new StringAttributeSensorAndConfigKey("elasticsearch.node.cluster.name", + "Cluster name (or elasticsearch selected if not set", null); + + AttributeSensor<String> NODE_ID = Sensors.newStringSensor("elasticsearch.node.id"); + AttributeSensor<Integer> DOCUMENT_COUNT = Sensors.newIntegerSensor("elasticsearch.node.docs.count"); + AttributeSensor<Integer> STORE_BYTES = Sensors.newIntegerSensor("elasticsearch.node.store.bytes"); + AttributeSensor<Integer> GET_TOTAL = Sensors.newIntegerSensor("elasticsearch.node.get.total"); + AttributeSensor<Integer> GET_TIME_IN_MILLIS = Sensors.newIntegerSensor("elasticsearch.node.get.time.in.millis"); + AttributeSensor<Integer> SEARCH_QUERY_TOTAL = Sensors.newIntegerSensor("elasticsearch.node.search.query.total"); + AttributeSensor<Integer> SEARCH_QUERY_TIME_IN_MILLIS = Sensors.newIntegerSensor("elasticsearch.node.search.query.time.in.millis"); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeDriver.java new file mode 100644 index 0000000..bc0e57c --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeDriver.java @@ -0,0 +1,25 @@ +/* + * 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.nosql.elasticsearch; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface ElasticSearchNodeDriver extends SoftwareProcessDriver { + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeImpl.java new file mode 100644 index 0000000..9c51de5 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeImpl.java @@ -0,0 +1,110 @@ +/* + * 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.nosql.elasticsearch; + +import static com.google.common.base.Preconditions.checkNotNull; +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.event.feed.http.JsonFunctions; +import brooklyn.location.access.BrooklynAccessUtils; +import brooklyn.util.guava.Functionals; +import brooklyn.util.guava.Maybe; +import brooklyn.util.guava.MaybeFunctions; +import brooklyn.util.guava.TypeTokens; +import brooklyn.util.http.HttpToolResponse; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.net.HostAndPort; +import com.google.gson.JsonElement; + +public class ElasticSearchNodeImpl extends SoftwareProcessImpl implements ElasticSearchNode { + + protected static final Function<Maybe<JsonElement>, Maybe<JsonElement>> GET_FIRST_NODE_FROM_NODES = new Function<Maybe<JsonElement>, Maybe<JsonElement>>() { + @Override public Maybe<JsonElement> apply(Maybe<JsonElement> input) { + if (input.isAbsent()) { + return input; + } + return Maybe.fromNullable(input.get().getAsJsonObject().entrySet().iterator().next().getValue()); + } + }; + + protected static final Function<HttpToolResponse, Maybe<JsonElement>> GET_FIRST_NODE = Functionals.chain(HttpValueFunctions.jsonContents(), + MaybeFunctions.<JsonElement>wrap(), JsonFunctions.walkM("nodes"), GET_FIRST_NODE_FROM_NODES); + + + HttpFeed httpFeed; + + @Override + public Class<ElasticSearchNodeDriver> getDriverInterface() { + return ElasticSearchNodeDriver.class; + } + + protected static final <T> HttpPollConfig<T> getSensorFromNodeStat(AttributeSensor<T> sensor, String... jsonPath) { + return new HttpPollConfig<T>(sensor) + .onSuccess(Functionals.chain(GET_FIRST_NODE, JsonFunctions.walkM(jsonPath), JsonFunctions.castM(TypeTokens.getRawRawType(sensor.getTypeToken()), null))) + .onFailureOrException(Functions.<T>constant(null)); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + Integer rawPort = getAttribute(HTTP_PORT); + checkNotNull(rawPort, "HTTP_PORT sensors not set for %s; is an acceptable port available?", this); + HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, rawPort); + Function<Maybe<JsonElement>, String> getNodeId = new Function<Maybe<JsonElement>, String>() { + @Override public String apply(Maybe<JsonElement> input) { + if (input.isAbsent()) { + return null; + } + return input.get().getAsJsonObject().entrySet().iterator().next().getKey(); + } + }; + httpFeed = HttpFeed.builder() + .entity(this) + .period(1000) + .baseUri(String.format("http://%s:%s/_nodes/_local/stats", hp.getHostText(), hp.getPort())) + .poll(new HttpPollConfig<Boolean>(SERVICE_UP) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .onFailureOrException(Functions.constant(false))) + .poll(new HttpPollConfig<String>(NODE_ID) + .onSuccess(Functionals.chain(HttpValueFunctions.jsonContents(), MaybeFunctions.<JsonElement>wrap(), JsonFunctions.walkM("nodes"), getNodeId)) + .onFailureOrException(Functions.constant(""))) + .poll(getSensorFromNodeStat(NODE_NAME, "name")) + .poll(getSensorFromNodeStat(DOCUMENT_COUNT, "indices", "docs", "count")) + .poll(getSensorFromNodeStat(STORE_BYTES, "indices", "store", "size_in_bytes")) + .poll(getSensorFromNodeStat(GET_TOTAL, "indices", "get", "total")) + .poll(getSensorFromNodeStat(GET_TIME_IN_MILLIS, "indices", "get", "time_in_millis")) + .poll(getSensorFromNodeStat(SEARCH_QUERY_TOTAL, "indices", "search", "query_total")) + .poll(getSensorFromNodeStat(SEARCH_QUERY_TIME_IN_MILLIS, "indices", "search", "query_time_in_millis")) + .poll(new HttpPollConfig<String>(CLUSTER_NAME) + .onSuccess(HttpValueFunctions.jsonContents("cluster_name", String.class))) + .build(); + } + + @Override + protected void disconnectSensors() { + if (httpFeed != null) { + httpFeed.stop(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeSshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeSshDriver.java new file mode 100644 index 0000000..74f53e5 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/elasticsearch/ElasticSearchNodeSshDriver.java @@ -0,0 +1,139 @@ +/* + * 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.nosql.elasticsearch; + +import static java.lang.String.format; + +import java.io.Reader; +import java.io.StringReader; +import java.util.List; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Urls; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; + +import com.google.common.collect.ImmutableList; + +public class ElasticSearchNodeSshDriver extends AbstractSoftwareProcessSshDriver implements ElasticSearchNodeDriver { + + public ElasticSearchNodeSshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public void preInstall() { + resolver = Entities.newDownloader(this); + setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("elasticsearch-%s", getVersion())))); + } + + @Override + public void install() { + List<String> urls = resolver.getTargets(); + String saveAs = resolver.getFilename(); + + List<String> commands = ImmutableList.<String>builder() + .add(BashCommands.installJavaLatestOrWarn()) + .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) + .add(String.format("tar zxvf %s", saveAs)) + .build(); + + newScript(INSTALLING).body.append(commands).execute(); + } + + @Override + public void customize() { + newScript(CUSTOMIZING).execute(); //create the directory + + String configFileUrl = entity.getConfig(ElasticSearchNode.TEMPLATE_CONFIGURATION_URL); + + if (configFileUrl == null) { + return; + } + + String configScriptContents = processTemplate(configFileUrl); + Reader configContents = new StringReader(configScriptContents); + + getMachine().copyTo(configContents, Urls.mergePaths(getRunDir(), getConfigFile())); + } + + @Override + public void launch() { + String pidFile = getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME; + entity.setAttribute(ElasticSearchNode.PID_FILE, pidFile); + StringBuilder commandBuilder = new StringBuilder() + .append(String.format("%s/bin/elasticsearch -d -p %s", getExpandedInstallDir(), pidFile)); + if (entity.getConfig(ElasticSearchNode.TEMPLATE_CONFIGURATION_URL) != null) { + commandBuilder.append(" -Des.config=" + Os.mergePaths(getRunDir(), getConfigFile())); + } + appendConfigIfPresent(commandBuilder, "es.path.data", ElasticSearchNode.DATA_DIR, Os.mergePaths(getRunDir(), "data")); + appendConfigIfPresent(commandBuilder, "es.path.logs", ElasticSearchNode.LOG_DIR, Os.mergePaths(getRunDir(), "logs")); + appendConfigIfPresent(commandBuilder, "es.node.name", ElasticSearchNode.NODE_NAME.getConfigKey()); + appendConfigIfPresent(commandBuilder, "es.cluster.name", ElasticSearchNode.CLUSTER_NAME.getConfigKey()); + appendConfigIfPresent(commandBuilder, "es.discovery.zen.ping.multicast.enabled", ElasticSearchNode.MULTICAST_ENABLED); + appendConfigIfPresent(commandBuilder, "es.discovery.zen.ping.unicast.enabled", ElasticSearchNode.UNICAST_ENABLED); + commandBuilder.append(" > out.log 2> err.log < /dev/null"); + newScript(MutableMap.of("usePidFile", false), LAUNCHING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(commandBuilder.toString()) + .execute(); + } + + private void appendConfigIfPresent(StringBuilder builder, String parameter, ConfigKey<?> configKey) { + appendConfigIfPresent(builder, parameter, configKey, null); + } + + private void appendConfigIfPresent(StringBuilder builder, String parameter, ConfigKey<?> configKey, String defaultValue) { + String config = null; + if (entity.getConfig(configKey) != null) { + config = String.valueOf(entity.getConfig(configKey)); + } + if (config == null && defaultValue != null) { + config = defaultValue; + } + if (config != null) { + builder.append(String.format(" -D%s=%s", parameter, config)); + } + } + + public String getConfigFile() { + return "elasticsearch.yaml"; + } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of("usePidFile", true), CHECK_RUNNING).execute() == 0; + } + + @Override + public void stop() { + newScript(MutableMap.of("usePidFile", true), STOPPING).execute(); + } + + @Override + public void kill() { + newScript(MutableMap.of("usePidFile", true), KILLING).execute(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBServer.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBServer.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBServer.java new file mode 100644 index 0000000..5f8cc84 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBServer.java @@ -0,0 +1,61 @@ +/* + * 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.nosql.mongodb; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.event.basic.AttributeSensorAndConfigKey; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.event.basic.PortAttributeSensorAndConfigKey; +import brooklyn.util.flags.SetFromFlag; + +public interface AbstractMongoDBServer extends SoftwareProcess, Entity { + + // TODO Need to properly test v2.4.x and v2.5.x support. + // I think the v2.5.x were dev releases. + // Should update mongo.config to yaml format, but no rush for that. + + @SetFromFlag("dataDirectory") + ConfigKey<String> DATA_DIRECTORY = ConfigKeys.newStringConfigKey( + "mongodb.data.directory", "Data directory to store MongoDB journals"); + + @SetFromFlag("mongodbConfTemplateUrl") + ConfigKey<String> MONGODB_CONF_TEMPLATE_URL = ConfigKeys.newStringConfigKey( + "mongodb.config.url", "Template file (in freemarker format) for a MongoDB configuration file", + "classpath://org/apache/brooklyn/entity/nosql/mongodb/default.conf"); + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = + ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "2.6.5"); + + // TODO: Windows support + // e.g. http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.2.2.tgz, + // http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-2.2.2.tgz + // http://downloads.mongodb.org/win32/mongodb-win32-x86_64-1.8.5.zip + // Note Windows download is a zip. + @SetFromFlag("downloadUrl") + AttributeSensorAndConfigKey<String, String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( + SoftwareProcess.DOWNLOAD_URL, "http://fastdl.mongodb.org/${driver.osDir}/${driver.osTag}-${version}.tgz"); + + @SetFromFlag("port") + PortAttributeSensorAndConfigKey PORT = + new PortAttributeSensorAndConfigKey("mongodb.server.port", "Server port", "27017+"); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java new file mode 100644 index 0000000..4065ec5 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java @@ -0,0 +1,175 @@ +/* + * 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.nosql.mongodb; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.basic.lifecycle.ScriptHelper; +import brooklyn.location.OsDetails; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public abstract class AbstractMongoDBSshDriver extends AbstractSoftwareProcessSshDriver { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractMongoDBSshDriver.class); + + public AbstractMongoDBSshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public void preInstall() { + resolver = Entities.newDownloader(this); + setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(getBaseName()))); + } + + @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("tar xzfv " + saveAs); + + newScript(INSTALLING) + .failOnNonZeroResultCode() + .body.append(commands).execute(); + } + + @Override + public void customize() { + Map<?,?> ports = ImmutableMap.of("port", getServerPort()); + Networking.checkPortsValid(ports); + String command = String.format("mkdir -p %s", getDataDirectory()); + newScript(CUSTOMIZING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(command).execute(); + String templateUrl = entity.getConfig(MongoDBServer.MONGODB_CONF_TEMPLATE_URL); + if (!Strings.isNullOrEmpty(templateUrl)) copyTemplate(templateUrl, getConfFile()); + } + + @Override + public boolean isRunning() { + try { + return MongoDBClientSupport.forServer((AbstractMongoDBServer) entity).ping(); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return false; + } + } + + /** + * Kills the server with SIGINT. Sending SIGKILL is likely to result in data corruption. + * @see <a href="http://docs.mongodb.org/manual/tutorial/manage-mongodb-processes/#sending-a-unix-int-or-term-signal">http://docs.mongodb.org/manual/tutorial/manage-mongodb-processes/#sending-a-unix-int-or-term-signal</a> + */ + @Override + public void stop() { + // TODO: Wait for process to terminate. Currently, this will send the signal and then immediately continue with next steps, + // which could involve stopping VM etc. + + // We could also use SIGTERM (15) + new ScriptHelper(this, "Send SIGINT to MongoDB server") + .body.append("kill -2 $(cat " + getPidFile() + ")") + .execute(); + } + + protected String getBaseName() { + return getOsTag() + "-" + entity.getConfig(AbstractMongoDBServer.SUGGESTED_VERSION); + } + + // IDE note: This is used by MongoDBServer.DOWNLOAD_URL + public String getOsDir() { + return (getLocation().getOsDetails().isMac()) ? "osx" : "linux"; + } + + public String getOsTag() { + OsDetails os = getLocation().getOsDetails(); + if (os == null) { + // Default to generic linux + return "mongodb-linux-x86_64"; + } else if (os.isMac()) { + // Mac is 64bit only + return "mongodb-osx-x86_64"; + } else { + String arch = os.is64bit() ? "x86_64" : "i686"; + return "mongodb-linux-" + arch; + } + } + + public String getDataDirectory() { + String result = entity.getConfig(MongoDBServer.DATA_DIRECTORY); + if (result!=null) return result; + return getRunDir() + "/data"; + } + + protected String getLogFile() { + return getRunDir() + "/log.txt"; + } + + protected String getPidFile() { + return getRunDir() + "/pid"; + } + + protected Integer getServerPort() { + return entity.getAttribute(MongoDBServer.PORT); + } + + protected String getConfFile() { + return getRunDir() + "/mongo.conf"; + } + + protected ImmutableList.Builder<String> getArgsBuilderWithDefaults(AbstractMongoDBServer server) { + Integer port = server.getAttribute(MongoDBServer.PORT); + + return ImmutableList.<String>builder() + .add("--config", getConfFile()) + .add("--pidfilepath", getPidFile()) + .add("--logpath", getLogFile()) + .add("--port", port.toString()) + .add("--fork"); + } + + protected void launch(ImmutableList.Builder<String> argsBuilder) { + String args = Joiner.on(" ").join(argsBuilder.build()); + String command = String.format("%s/bin/mongod %s > out.log 2> err.log < /dev/null", getExpandedInstallDir(), args); + LOG.info(command); + newScript(LAUNCHING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(command).execute(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClient.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClient.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClient.java new file mode 100644 index 0000000..b2eeb59 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClient.java @@ -0,0 +1,65 @@ +/* + * 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.nosql.mongodb; + +import java.util.List; +import java.util.Map; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.annotation.Effector; +import brooklyn.entity.annotation.EffectorParam; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.MethodEffector; +import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBShardedDeployment; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.util.flags.SetFromFlag; + +import com.google.common.reflect.TypeToken; + +@ImplementedBy(MongoDBClientImpl.class) +public interface MongoDBClient extends AbstractMongoDBServer { + + MethodEffector<Void> RUN_SCRIPT = new MethodEffector<Void>(MongoDBClient.class, "runScript"); + + @SuppressWarnings("serial") + @SetFromFlag("startupJsScripts") + ConfigKey<List<String>> STARTUP_JS_SCRIPTS = ConfigKeys.newConfigKey( + new TypeToken<List<String>>(){}, "mongodb.client.startupJsScripts", + "List of scripts defined in mongodb.client.scripts to be run on startup"); + + @SuppressWarnings("serial") + @SetFromFlag("scripts") + ConfigKey<Map<String, String>> JS_SCRIPTS = ConfigKeys.newConfigKey( + new TypeToken<Map<String, String>>(){}, "mongodb.client.scripts", "List of javascript scripts to be copied " + + "to the server. These scripts can be run using the runScript effector"); + + @SetFromFlag("shardedDeployment") + ConfigKey<MongoDBShardedDeployment> SHARDED_DEPLOYMENT = ConfigKeys.newConfigKey(MongoDBShardedDeployment.class, + "mongodb.client.shardeddeployment", "Sharded deployment that the client will use to run scripts. " + + "If both SERVER and SHARDED_DEPLOYMENT are specified, SERVER will be used"); + + @SetFromFlag("server") + ConfigKey<AbstractMongoDBServer> SERVER = ConfigKeys.newConfigKey(AbstractMongoDBServer.class, + "mongodb.client.server", "MongoDBServer that the client will use to run scripts. " + + "If both SERVER and SHARDED_DEPLOYMENT are specified, SERVER will be used"); + + @Effector(description="Runs one of the scripts defined in mongodb.client.scripts") + void runScript(@EffectorParam(name="preStart", description="use this to create parameters that can be used by the script, e.g.:<p><code>var loopCount = 10</code>") String preStart, + @EffectorParam(name="scriptName", description="Name of the script as defined in mongodb.client.scripts") String scriptName); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientDriver.java new file mode 100644 index 0000000..4bbfabd --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientDriver.java @@ -0,0 +1,25 @@ +/* + * 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.nosql.mongodb; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface MongoDBClientDriver extends SoftwareProcessDriver { + void runScript(String preStart, String scriptName); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientImpl.java new file mode 100644 index 0000000..bff69e9 --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientImpl.java @@ -0,0 +1,43 @@ +/* + * 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.nosql.mongodb; + +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.entity.trait.Startable; + +public class MongoDBClientImpl extends SoftwareProcessImpl implements MongoDBClient { + + @Override + protected void connectSensors() { + super.connectSensors(); + setAttribute(Startable.SERVICE_UP, true); + } + + @SuppressWarnings("rawtypes") + @Override + public Class getDriverInterface() { + return MongoDBClientDriver.class; + } + + @Override + public void runScript(String preStart, String scriptName) { + ((MongoDBClientDriver)getDriver()).runScript(preStart, scriptName); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientSshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientSshDriver.java new file mode 100644 index 0000000..50ca2ad --- /dev/null +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBClientSshDriver.java @@ -0,0 +1,147 @@ +/* + * 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.nosql.mongodb; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.EntityLocal; +import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBRouter; +import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBRouterCluster; +import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBShardedDeployment; +import brooklyn.entity.trait.Startable; +import brooklyn.event.basic.DependentConfiguration; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.math.MathPredicates; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; + +public class MongoDBClientSshDriver extends AbstractMongoDBSshDriver implements MongoDBClientDriver { + + private static final Logger LOG = LoggerFactory.getLogger(MongoDBClientSshDriver.class); + + private boolean isRunning = false; + + public MongoDBClientSshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public void customize() { + String command = String.format("mkdir -p %s", getUserScriptDir()); + newScript(CUSTOMIZING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(command).execute(); + Map<String, String> scripts = entity.getConfig(MongoDBClient.JS_SCRIPTS); + for (String scriptName : scripts.keySet()) { + copyResource(scripts.get(scriptName), getUserScriptDir() + scriptName + ".js"); + } + } + + @Override + public void launch() { + AbstractMongoDBServer server = getServer(); + // The scripts are going to be run on the machine via SSH so it shouldn't matter + // that the accessible host and port might be different. + String host = server.getAttribute(AbstractMongoDBServer.HOSTNAME); + Integer port = server.getAttribute(AbstractMongoDBServer.PORT); + + List<String> scripts = entity.getConfig(MongoDBClient.STARTUP_JS_SCRIPTS); + if (scripts!=null) { + for (String scriptName : scripts) { + try { + LOG.debug("Running MongoDB script "+scriptName+" at "+getEntity()); + runScript("", scriptName, host, port); + } catch (Exception e) { + LOG.warn("Error running MongoDB script "+scriptName+" at "+getEntity()+", throwing: "+e); + isRunning = false; + Exceptions.propagateIfFatal(e); + throw new IllegalStateException("Error running MongoDB script "+scriptName+" at "+entity+": "+e, e); + } + } + } + isRunning = true; + } + + @Override + public boolean isRunning() { + // TODO better would be to get some confirmation + return isRunning; + } + + @Override + public void stop() { + try { + super.stop(); + } finally { + isRunning = false; + } + } + + private String getUserScriptDir() { + return getRunDir() + "/userScripts/" ; + } + + public void runScript(String preStart, String scriptName) { + AbstractMongoDBServer server = getServer(); + String host = server.getAttribute(AbstractMongoDBServer.HOSTNAME); + Integer port = server.getAttribute(AbstractMongoDBServer.PORT); + runScript(preStart, scriptName, host, port); + } + + private void runScript(String preStart, String scriptName, String host, Integer port) { + // TODO: escape preStart to prevent injection attack + String command = String.format("%s/bin/mongo %s:%s --eval \"%s\" %s/%s > out.log 2> err.log < /dev/null", getExpandedInstallDir(), + host, port, preStart, getUserScriptDir(), scriptName + ".js"); + newScript(LAUNCHING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(command).execute(); + } + + private AbstractMongoDBServer getServer() { + AbstractMongoDBServer server = entity.getConfig(MongoDBClient.SERVER); + MongoDBShardedDeployment deployment = entity.getConfig(MongoDBClient.SHARDED_DEPLOYMENT); + if (server == null) { + Preconditions.checkNotNull(deployment, "Either server or shardedDeployment must be specified for %s", this); + server = DependentConfiguration.builder() + .attributeWhenReady(deployment.getRouterCluster(), MongoDBRouterCluster.ANY_ROUTER) + .blockingDetails("any available router") + .runNow(); + DependentConfiguration.builder() + .attributeWhenReady(server, MongoDBRouter.SHARD_COUNT) + .readiness(MathPredicates.<Integer>greaterThan(0)) + .runNow(); + } else { + if (deployment != null) { + log.warn("Server and ShardedDeployment defined for {}; using server ({} instead of {})", + new Object[] {this, server, deployment}); + } + DependentConfiguration.builder() + .attributeWhenReady(server, Startable.SERVICE_UP) + .readiness(Predicates.equalTo(true)) + .runNow(); + } + return server; + } +}
