com.bazaarvoice.emodb.sdk.EmoStartMojo Maven / Gradle / Ivy
package com.bazaarvoice.emodb.sdk;
import com.bazaarvoice.emodb.common.json.JsonHelper;
import com.bazaarvoice.emodb.web.auth.ApiKeyEncryption;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Closeables;
import com.sun.jersey.api.client.Client;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.test.TestingServer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.twdata.maven.mojoexecutor.MojoExecutor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
@Mojo(name = "start", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST)
public class EmoStartMojo extends AbstractEmoMojo {
/** Computed. */
private int cassandraStopPort;
private int cassandraJmxPort;
private int cassandraStoragePort;
private int cassandraNativeTransportPort;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (skip) {
getLog().info("Skipping emodb start...");
return;
}
try {
if (autoStartZookeeper) {
startZookeeper();
}
if (autoStartCassandra) {
startCassandra();
}
if (autoStartEmo) {
installEmoFiles();
startEmoAndWaitUntilHealthy();
}
initializeRolesAndApiKeys();
if (waitForInterrupt) {
sleepUntilInterrupted();
}
} catch (Exception e) {
getLog().error(e);
}
}
private void installEmoFiles() throws MojoExecutionException, MojoFailureException, IOException {
copyEmoServiceArtifactToWorkingDirectory();
copyEmoConfigurationFile();
copyDdlConfigurationFile();
}
private void startEmoAndWaitUntilHealthy() throws MojoExecutionException, MojoFailureException, IOException {
final EmoExec emoProcess = new EmoExec();
emoProcess.setMaxMemoryMegabytes(emoMaxMemory);
emoProcess.setDebugPort(emoDebugPort);
emoProcess.setSuspendDebugOnStartup(suspendDebugOnStartup);
if (null != emoLogFile) {
emoProcess.setEmoLogFile(new File(emoLogFile));
}
emoProcess.execute(emoProcessWorkingDirectory(), getLog(), "server", "conf/config.yaml", "conf/config-ddl.yaml");
CrossMojoState.addEmoProcess(emoProcess, getPluginContext());
getLog().info("Waiting for healthy instance...");
EmoHealthCondition.waitSecondsUntilHealthy(healthCheckPort, 60);
getLog().info("healthy!");
}
private void copyEmoConfigurationFile() throws MojoExecutionException, IOException {
if (StringUtils.isBlank(emoConfigurationFile)) {
copyDefaultEmoConfigurationFile();
} else {
try {
// copy configuration file to well-known emodb config directory and filename "config.yaml"
FileUtils.copyFile(new File(emoConfigurationFile), new File(emoConfigurationDirectory(), "config.yaml"));
} catch (Exception e) {
throw new MojoExecutionException("failed to copy configuration file from " + emoConfigurationFile, e);
}
}
}
private void copyDdlConfigurationFile() throws MojoExecutionException, IOException {
if (StringUtils.isBlank(ddlConfigurationFile)) {
copyDefaultDdlConfigurationFile();
} else {
try {
// copy configuration file to well-known emodb DDL config directory and filename "config-ddl.yaml"
FileUtils.copyFile(new File(ddlConfigurationFile), new File(emoConfigurationDirectory(), "config-ddl.yaml"));
} catch (Exception e) {
throw new MojoExecutionException("failed to copy configuration file from " + ddlConfigurationFile, e);
}
}
}
private void copyEmoServiceArtifactToWorkingDirectory() throws MojoExecutionException, MojoFailureException {
// NOTE: this emo service artifact is an "uberjar" created by the maven-shade-plugin
final ArtifactItem emoServiceArtifact = new ArtifactItem();
emoServiceArtifact.setGroupId("com.bazaarvoice.emodb");
emoServiceArtifact.setArtifactId("emodb-web");
emoServiceArtifact.setVersion(pluginVersion());
resolveArtifactItems(asList(emoServiceArtifact));
final File emoServiceJar = emoServiceArtifact.getResolvedArtifact().getArtifact().getFile();
try {
// copy to "emodb.jar" so that we always overwrite any prior
FileUtils.copyFile(emoServiceJar, new File(emoProcessWorkingDirectory(), "emodb.jar"));
} catch (IOException e) {
throw new MojoFailureException("failed to copy: " + emoServiceArtifact, e);
}
}
private void startZookeeper() {
try {
getLog().info("Starting zookeeper on port " + zookeeperPort);
// ZooKeeper is too noisy.
System.setProperty("org.slf4j.simpleLogger.log.org.apache.zookeeper", "error");
final TestingServer zookeeperTestingServer = new TestingServer(zookeeperPort);
CrossMojoState.putZookeeperTestingServer(zookeeperTestingServer, getPluginContext());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void startCassandra() throws MojoExecutionException {
// find first available port and optimistically assume the subsequent ports are available.
getLog().info("cassandraRpcPort = " + cassandraRpcPort);
cassandraStopPort = cassandraRpcPort + 1;
getLog().info("cassandraStopPort = " + cassandraStopPort);
cassandraJmxPort = cassandraRpcPort + 2;
getLog().info("cassandraJmxPort = " + cassandraJmxPort);
cassandraStoragePort = cassandraRpcPort + 3;
getLog().info("cassandraStoragePort = " + cassandraStoragePort);
cassandraNativeTransportPort = cassandraRpcPort + 4;
getLog().info("cassandraNativeTransportPort = " + cassandraNativeTransportPort);
executeMojo(CASSANDRA_PLUGIN,
goal("start"), cassandraStartConfiguration(),
executionEnvironment(project, session, pluginManager)
);
CrossMojoState.putCassandraStopPort(cassandraStopPort, getPluginContext());
}
protected final Xpp3Dom cassandraStartConfiguration() {
ImmutableList.Builder elementBuilder = ImmutableList.builder()
.add(element("maxMemory", String.valueOf(cassandraMaxMemory)))
.add(element("rpcPort", String.valueOf(cassandraRpcPort)))
.add(element("stopPort", String.valueOf(cassandraStopPort)))
.add(element("jmxPort", String.valueOf(cassandraJmxPort)))
.add(element("storagePort", String.valueOf(cassandraStoragePort)))
.add(element("nativeTransportPort", String.valueOf(cassandraNativeTransportPort)))
.add(element("skip", String.valueOf(false)))
.add(element("loadAfterFirstStart", String.valueOf(false)))
.add(element("startNativeTransport", String.valueOf(true)))
.add(element("yaml", "partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner")); // random is the default
if (cassandraDir != null) {
elementBuilder.add(element("cassandraDir", cassandraDir));
}
List elements = elementBuilder.build();
return configuration(elements.toArray(new MojoExecutor.Element[elements.size()]));
}
/** The version of this plugin used; is also the same version of the emodb server to use. */
private String pluginVersion() {
return ((PluginDescriptor) getPluginContext().get("pluginDescriptor")).getVersion();
}
private File emoProcessWorkingDirectory() {
return ensureDirectory(new File(project.getBuild().getDirectory(), emoDir));
}
private File emoConfigurationDirectory() {
return ensureDirectory(new File(emoProcessWorkingDirectory(), "conf"));
}
private List resolveArtifactItems(List artifactItems) throws MojoExecutionException {
// resolved artifacts have been downloaded and are available locally
for (ArtifactItem item : artifactItems) {
try {
item.setResolvedArtifact(repositorySystem.resolveArtifact(repositorySystemSession, toArtifactRequest(item)));
} catch (ArtifactResolutionException e) {
throw new MojoExecutionException("couldn't resolve: " + item, e);
}
}
return artifactItems;
}
private ArtifactRequest toArtifactRequest(ArtifactItem item) {
return new ArtifactRequest(toDefaultArtifact(item), project.getRemoteProjectRepositories(), "project");
}
private org.eclipse.aether.artifact.Artifact toDefaultArtifact(ArtifactItem item) {
return new DefaultArtifact(item.getGroupId(), item.getArtifactId(), item.getClassifier(), item.getType()/*extension*/, item.getVersion());
}
private void copyDefaultEmoConfigurationFile() throws MojoExecutionException, IOException {
InputStream source = null;
FileOutputStream target = null;
try {
source = EmoStartMojo.class.getResourceAsStream("/emodb-default-config.yaml");
target = new FileOutputStream(new File(emoConfigurationDirectory(), "config.yaml"));
IOUtils.copy(source, target);
} catch (IOException e) {
throw new MojoExecutionException("could not find the default configuration file");
} finally {
Closeables.close(source, false);
Closeables.close(target, false);
}
}
private void copyDefaultDdlConfigurationFile() throws MojoExecutionException, IOException {
InputStream source = null;
FileOutputStream target = null;
try {
source = EmoStartMojo.class.getResourceAsStream("/emodb-default-config-ddl.yaml");
target = new FileOutputStream(new File(emoConfigurationDirectory(), "config-ddl.yaml"));
IOUtils.copy(source, target);
} catch (IOException e) {
throw new MojoExecutionException("could not find the ddl configuration file");
} finally {
Closeables.close(source, false);
Closeables.close(target, false);
}
}
private void sleepUntilInterrupted() throws IOException {
getLog().info("Hit ENTER on the console to continue the build.");
for (;;) {
int ch = System.in.read();
if (ch == -1 || ch == '\n') {
break;
}
}
}
private void initializeRolesAndApiKeys() throws MojoFailureException {
List roles = this.roles != null ? Arrays.asList(this.roles) : ImmutableList.of();
List apiKeys = this.apiKeys != null ? Arrays.asList(this.apiKeys) : ImmutableList.of();
if (roles.isEmpty() && apiKeys.isEmpty()) {
// Nothing to create
return;
}
Client client = Client.create();
try {
// Parse the config file to get the admin URI and API key. To support as much forward compatibility as
// possible deserialize the yaml into the minimum representation required.
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
MinimalEmoConfiguration config = objectMapper.readValue(new File(emoConfigurationDirectory(), "config.yaml"), MinimalEmoConfiguration.class);
URI adminUri = getEmoUri(config);
String adminApiKey = getAdminApiKey(config);
for (RoleParameter role : roles) {
String name = requireNonNull(role.getName(), "Role configuration must include a name");
getLog().info("Creating role " + name);
Map entity = ImmutableMap.of(
"name", role.getName(),
"permissions", ImmutableSet.copyOf(role.getPermissions()));
String response = client.resource(adminUri)
.path("uac")
.path("1")
.path("role")
.path("_")
.path(name)
.queryParam("APIKey", adminApiKey)
.type("application/x.json-create-role")
.post(String.class, JsonHelper.asJson(entity));
getLog().info("Response to create role " + name);
getLog().info(response);
}
for (ApiKeyParameter apiKey : apiKeys) {
String value = requireNonNull(apiKey.getValue(), "API key configuration must include a value");
getLog().info("Creating API key " + value);
Map entity = ImmutableMap.of(
"owner", "emodb-sdk",
"roles", Arrays.stream(apiKey.getRoles())
.map(role -> ImmutableMap.of("id", role))
.collect(Collectors.toList()));
String response = client.resource(adminUri)
.path("uac")
.path("1")
.path("api-key")
.queryParam("APIKey", adminApiKey)
.queryParam("key", value)
.type("application/x.json-create-api-key")
.post(String.class, JsonHelper.asJson(entity));
getLog().info("Response to create API key " + value);
getLog().info(response);
}
} catch (Exception e) {
throw new MojoFailureException("Failed to initialize roles and API keys", e);
} finally {
client.destroy();
}
}
private URI getEmoUri(MinimalEmoConfiguration config) {
return URI.create(String.format("http://localhost:%d", config.server.applicationConnectors.get(0).port));
}
private String getAdminApiKey(MinimalEmoConfiguration config) {
return new ApiKeyEncryption(config.cluster).decrypt(config.auth.adminApiKey);
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MinimalEmoConfiguration {
public String cluster;
public MinimalAuthConfiguration auth;
public MinimalServerConfiguration server;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MinimalAuthConfiguration {
public String adminApiKey;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MinimalServerConfiguration {
public List applicationConnectors;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MinimalApplicationConnectorConfiguration {
public int port;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy