Merge pull request #102 from nosqlbench/docker-metrics-refactor

Docker metrics refactor
This commit is contained in:
Sebastián Estévez 2020-03-26 22:19:09 -04:00 committed by GitHub
commit 373f7c3322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 589 additions and 493 deletions

View File

@ -11,7 +11,7 @@ import io.nosqlbench.engine.core.MarkdownDocInfo;
import io.nosqlbench.engine.core.ScenarioLogger;
import io.nosqlbench.engine.core.ScenariosResults;
import io.nosqlbench.engine.core.ShutdownManager;
import io.nosqlbench.engine.docker.DockerMetricsHelper;
import io.nosqlbench.engine.docker.DockerMetricsManager;
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
import io.nosqlbench.engine.core.metrics.MetricReporters;
import io.nosqlbench.engine.core.script.MetricsMapper;
@ -132,7 +132,7 @@ public class NBCLI {
String reportGraphiteTo = options.wantsReportGraphiteTo();
if (options.wantsDockerMetrics()){
logger.info("Docker metrics is enabled. Docker must be installed for this to work");
DockerMetricsHelper dmh= new DockerMetricsHelper();
DockerMetricsManager dmh= new DockerMetricsManager();
dmh.startMetrics();
logger.info("Docker Containers are started, for grafana and prometheus, hit" +
"these urls in your browser: http://<host>:3000 and http://<host>:9090" +

View File

@ -0,0 +1,245 @@
package io.nosqlbench.engine.docker;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.DockerCmdExecFactory;
import com.github.dockerjava.api.command.ListContainersCmd;
import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.async.ResultCallbackTemplate;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback;
import com.github.dockerjava.okhttp.OkHttpDockerCmdExecFactory;
import com.sun.security.auth.module.UnixSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.nosqlbench.engine.docker.RestHelper.post;
public class DockerHelper {
private static final String DOCKER_HOST = "DOCKER_HOST";
private static final String DOCKER_HOST_ADDR = "unix:///var/run/docker.sock";
// private Client rsClient = ClientBuilder.newClient();
private DockerClientConfig config;
private DockerClient dockerClient;
private Logger logger = LoggerFactory.getLogger(DockerHelper.class);
public DockerHelper(){
System.getProperties().setProperty(DOCKER_HOST, DOCKER_HOST_ADDR);
this.config = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(DOCKER_HOST_ADDR).build();
DockerCmdExecFactory dockerCmdExecFactory = new OkHttpDockerCmdExecFactory()
.withReadTimeout(60000)
.withConnectTimeout(60000);
this.dockerClient = DockerClientBuilder.getInstance(config)
.withDockerCmdExecFactory(dockerCmdExecFactory)
.build();
}
public String startDocker(String IMG, String tag, String name, List<Integer> ports, List<String> volumeDescList, List<String> envList, List<String> cmdList, String reload) {
logger.debug("Starting docker with img=" + IMG + ", tag=" + tag + ", name=" + name + ", " +
"ports=" + ports + ", volumes=" + volumeDescList + ", env=" + envList + ", cmds=" + cmdList + ", reload=" + reload);
boolean existingContainer = removeExitedContainers(name);
/*
if(startStoppedContainer(name)){
return null;
};
*/
Container containerId = searchContainer(name, reload);
if (containerId != null) {
logger.debug("container is already up with the id: "+ containerId.getId());
return null;
}
Info info = dockerClient.infoCmd().exec();
dockerClient.buildImageCmd();
String term = IMG.split("/")[1];
//List<SearchItem> dockerSearch = dockerClient.searchImagesCmd(term).exec();
List<Image> dockerList = dockerClient.listImagesCmd().withImageNameFilter(IMG).exec();
if (dockerList.size() == 0) {
dockerClient.pullImageCmd(IMG)
.withTag(tag)
.exec(new PullImageResultCallback()).awaitSuccess();
dockerList = dockerClient.listImagesCmd().withImageNameFilter(IMG).exec();
if (dockerList.size() == 0) {
logger.error(String.format("Image %s not found, unable to automatically pull image." +
" Check `docker images`",
IMG));
System.exit(1);
}
}
logger.info("Search returned" + dockerList.toString());
List<ExposedPort> tcpPorts = new ArrayList<>();
List<PortBinding> portBindings = new ArrayList<>();
for (Integer port : ports) {
ExposedPort tcpPort = ExposedPort.tcp(port);
Ports.Binding binding = new Ports.Binding("0.0.0.0", String.valueOf(port));
PortBinding pb = new PortBinding(binding, tcpPort);
tcpPorts.add(tcpPort);
portBindings.add(pb);
}
List<Volume> volumeList = new ArrayList<>();
List<Bind> volumeBindList = new ArrayList<>();
for (String volumeDesc : volumeDescList) {
String volFrom = volumeDesc.split(":")[0];
String volTo = volumeDesc.split(":")[1];
Volume vol = new Volume(volTo);
volumeList.add(vol);
volumeBindList.add(new Bind(volFrom, vol));
}
CreateContainerResponse containerResponse;
if (envList == null) {
containerResponse = dockerClient.createContainerCmd(IMG + ":" + tag)
.withCmd(cmdList)
.withExposedPorts(tcpPorts)
.withHostConfig(
new HostConfig()
.withPortBindings(portBindings)
.withPublishAllPorts(true)
.withBinds(volumeBindList)
)
.withName(name)
//.withVolumes(volumeList)
.exec();
} else {
long user = new UnixSystem().getUid();
containerResponse = dockerClient.createContainerCmd(IMG + ":" + tag)
.withEnv(envList)
.withExposedPorts(tcpPorts)
.withHostConfig(
new HostConfig()
.withPortBindings(portBindings)
.withPublishAllPorts(true)
.withBinds(volumeBindList)
)
.withName(name)
.withUser(""+user)
//.withVolumes(volumeList)
.exec();
}
dockerClient.startContainerCmd(containerResponse.getId()).exec();
if (existingContainer){
logger.debug("Started existing container");
return null;
}
return containerResponse.getId();
}
private boolean startStoppedContainer(String name) {
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd().withStatusFilter(List.of("stopped"));
listContainersCmd.getFilters().put("name", Arrays.asList(name));
List<Container> stoppedContainers = null;
try {
stoppedContainers = listContainersCmd.exec();
for (Container stoppedContainer : stoppedContainers) {
String id = stoppedContainer.getId();
logger.info("Removing exited container: " + id);
dockerClient.removeContainerCmd(id).exec();
}
} catch (Exception e) {
e.printStackTrace();
logger.error("Unable to contact docker, make sure docker is up and try again.");
logger.error("If docker is installed make sure this user has access to the docker group.");
logger.error("$ sudo gpasswd -a ${USER} docker && newgrp docker");
System.exit(1);
}
return false;
}
private boolean removeExitedContainers(String name) {
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd().withStatusFilter(List.of("exited"));
listContainersCmd.getFilters().put("name", Arrays.asList(name));
List<Container> stoppedContainers = null;
try {
stoppedContainers = listContainersCmd.exec();
for (Container stoppedContainer : stoppedContainers) {
String id = stoppedContainer.getId();
logger.info("Removing exited container: " + id);
dockerClient.removeContainerCmd(id).exec();
return true;
}
} catch (Exception e) {
e.printStackTrace();
logger.error("Unable to contact docker, make sure docker is up and try again.");
logger.error("If docker is installed make sure this user has access to the docker group.");
logger.error("$ sudo gpasswd -a ${USER} docker && newgrp docker");
System.exit(1);
}
return false;
}
public Container searchContainer(String name, String reload) {
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd().withStatusFilter(List.of("running"));
listContainersCmd.getFilters().put("name", Arrays.asList(name));
List<Container> runningContainers = null;
try {
runningContainers = listContainersCmd.exec();
} catch (Exception e) {
e.printStackTrace();
logger.error("Unable to contact docker, make sure docker is up and try again.");
System.exit(1);
}
if (runningContainers.size() >= 1) {
//Container test = runningContainers.get(0);
logger.info(String.format("The container %s is already running", name));
logger.info(String.format("Hupping config"));
if (reload != null) {
post(reload, null, false, "reloading config");
}
return runningContainers.get(0);
}
return null;
}
public void pollLog(String containerId, ResultCallbackTemplate<LogContainerResultCallback, Frame> logCallback) {
LogContainerResultCallback loggingCallback = new
LogContainerResultCallback();
LogContainerCmd cmd = dockerClient.logContainerCmd(containerId)
.withStdOut(true)
.withFollowStream(true)
.withTailAll();
final boolean[] httpStarted = {false};
cmd.exec(logCallback);
try {
loggingCallback.awaitCompletion(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.error("Error getting docker log and detect start for containerId: " + containerId);
e.printStackTrace();
}
}
}

View File

@ -1,491 +0,0 @@
package io.nosqlbench.engine.docker;
/*
*
* @author Sebastián Estévez on 4/4/19.
*
*/
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.DockerCmdExecFactory;
import com.github.dockerjava.api.command.ListContainersCmd;
import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.async.ResultCallbackTemplate;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback;
//import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory;
import com.github.dockerjava.okhttp.OkHttpDockerCmdExecFactory;
import com.sun.security.auth.module.UnixSystem;
import io.nosqlbench.engine.api.exceptions.BasicError;
import io.nosqlbench.engine.api.util.NosqlBenchFiles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.Authenticator;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
//import io.nosqlbench.util.nosqlbenchFiles;
public class DockerMetricsHelper {
private static final String DOCKER_HOST = "DOCKER_HOST";
private static final String DOCKER_HOST_ADDR = "unix:///var/run/docker.sock";
String userHome = System.getProperty("user.home");
// private Client rsClient = ClientBuilder.newClient();
private DockerClientConfig config;
private DockerClient dockerClient;
private Logger logger = LoggerFactory.getLogger(DockerMetricsHelper.class);
public DockerMetricsHelper() {
System.getProperties().setProperty(DOCKER_HOST, DOCKER_HOST_ADDR);
this.config = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(DOCKER_HOST_ADDR).build();
DockerCmdExecFactory dockerCmdExecFactory = new OkHttpDockerCmdExecFactory()
.withReadTimeout(60000)
.withConnectTimeout(60000);
// DockerCmdExecFactory dockerCmdExecFactory = new JerseyDockerCmdExecFactory()
// .withReadTimeout(1000)
// .withConnectTimeout(1000);
this.dockerClient = DockerClientBuilder.getInstance(config)
.withDockerCmdExecFactory(dockerCmdExecFactory)
.build();
}
public void startMetrics() {
logger.info("preparing to start graphite exporter container...");
//docker run -d -p 9108:9108 -p 9109:9109 -p 9109:9109/udp prom/graphite-exporter
String GRAPHITE_EXPORTER_IMG = "prom/graphite-exporter";
String tag = "latest";
String name = "graphite-exporter";
//TODO: look into UDP
List<Integer> port = Arrays.asList(9108, 9109);
List<String> volumeDescList = Arrays.asList();
List<String> envList = Arrays.asList();
String reload = null;
startDocker(GRAPHITE_EXPORTER_IMG, tag, name, port, volumeDescList, envList, null, reload);
logger.info("graphite exporter container started");
logger.info("searching for graphite exporter container ip");
ContainerNetworkSettings settings = searchContainer(name, null).getNetworkSettings();
Map<String, ContainerNetwork> networks = settings.getNetworks();
String ip = null;
for (String key : networks.keySet()) {
ContainerNetwork network = networks.get(key);
ip = network.getIpAddress();
}
logger.info("preparing to start docker metrics");
String PROMETHEUS_IMG = "prom/prometheus";
tag = "v2.4.3";
name = "prom";
port = Arrays.asList(9090);
setupPromFiles(ip);
volumeDescList = Arrays.asList(
//cwd+"/docker-metrics/prometheus:/prometheus",
userHome + "/.nosqlbench/prometheus-conf:/etc/prometheus",
userHome + "/.nosqlbench/prometheus:/prometheus"
//"./prometheus/tg_dse.json:/etc/prometheus/tg_dse.json"
);
envList = null;
List<String> cmdList = Arrays.asList(
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--storage.tsdb.retention=183d",
"--web.enable-lifecycle"
);
reload = "http://localhost:9090/-/reload";
startDocker(PROMETHEUS_IMG, tag, name, port, volumeDescList, envList, cmdList, reload);
String GRAFANA_IMG = "grafana/grafana";
tag = "5.3.2";
name = "grafana";
port = Arrays.asList(3000);
setupGrafanaFiles(ip);
volumeDescList = Arrays.asList(
userHome+"/.nosqlbench/grafana:/var/lib/grafana:rw"
//cwd+"/docker-metrics/grafana:/grafana",
//cwd+"/docker-metrics/grafana/datasources:/etc/grafana/provisioning/datasources",
//cwd+"/docker-metrics/grafana/dashboardconf:/etc/grafana/provisioning/dashboards"
//,cwd+"/docker-metrics/grafana/dashboards:/var/lib/grafana/dashboards:ro"
);
envList = Arrays.asList(
"GF_SECURITY_ADMIN_PASSWORD=admin",
"GF_AUTH_ANONYMOUS_ENABLED=\"true\"",
"GF_SNAPSHOTS_EXTERNAL_SNAPSHOT_URL=https://assethub.datastax.com:3001",
"GF_SNAPSHOTS_EXTERNAL_SNAPSHOT_NAME=\"Upload to DataStax\""
);
reload = null;
String containerId = startDocker(GRAFANA_IMG, tag, name, port, volumeDescList, envList, null, reload);
LogContainerResultCallback loggingCallback = new
LogContainerResultCallback();
try {
LogContainerCmd cmd = dockerClient.logContainerCmd(containerId)
.withStdOut(true)
.withFollowStream(true)
.withTailAll();
final boolean[] httpStarted = {false};
cmd.exec(new LogCallback());
loggingCallback.awaitCompletion(10, TimeUnit.SECONDS);
logger.info("grafana container started, http listenning");
configureGrafana();
} catch (InterruptedException e) {
e.printStackTrace();
logger.error("unable to detect grafana start");
}
}
private void setupPromFiles(String ip) {
String datasource = NosqlBenchFiles.readFile("docker/prometheus/prometheus.yml");
if (ip == null) {
logger.error("IP for graphite container not found");
System.exit(1);
}
datasource = datasource.replace("!!!GRAPHITE_IP!!!", ip);
File nosqlbenchDir = new File(userHome, "/.nosqlbench/");
mkdir(nosqlbenchDir);
File prometheusDir = new File(userHome, "/.nosqlbench/prometheus");
mkdir(prometheusDir);
File promConfDir = new File(userHome, "/.nosqlbench/prometheus-conf");
mkdir(promConfDir);
Path prometheusDirPath = Paths.get(userHome, "/.nosqlbench" +
"/prometheus");
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OTHERS_READ);
perms.add(PosixFilePermission.OTHERS_WRITE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
try {
Files.setPosixFilePermissions(prometheusDirPath, perms);
} catch (IOException e) {
logger.error("failed to set permissions on prom backup " +
"directory " + userHome + "/.nosqlbench/prometheus)");
e.printStackTrace();
System.exit(1);
}
try (PrintWriter out = new PrintWriter(
new FileWriter(userHome + "/.nosqlbench/prometheus-conf" +
"/prometheus.yml", false))) {
out.println(datasource);
} catch (FileNotFoundException e) {
e.printStackTrace();
logger.error("error writing prometheus yaml file to ~/.prometheus");
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
logger.error("creating file in ~/.prometheus");
System.exit(1);
}
}
private void mkdir(File dir) {
if(dir.exists()){
return;
}
if(! dir.mkdir()){
if( dir.canWrite()){
System.out.println("no write access");
}
if( dir.canRead()){
System.out.println("no read access");
}
System.out.println("Could not create directory " + dir.getPath());
System.out.println("fix directory permissions to run --docker-metrics");
System.exit(1);
}
}
private void setupGrafanaFiles(String ip) {
File grafanaDir = new File(userHome, "/.nosqlbench/grafana");
mkdir(grafanaDir);
Path grafanaDirPath = Paths.get(userHome, "/.nosqlbench/grafana");
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.OWNER_EXECUTE);
try {
Files.setPosixFilePermissions(grafanaDirPath, perms);
} catch (IOException e) {
logger.error("failed to set permissions on grafana directory " +
"directory " + userHome + "/.nosqlbench/grafana)");
e.printStackTrace();
System.exit(1);
}
}
private void configureGrafana() {
post("http://localhost:3000/api/dashboards/db", "docker/dashboards/analysis.json", true, "load analysis dashboard");
post("http://localhost:3000/api/datasources", "docker/datasources/prometheus-datasource.yaml", true, "configure data source");
}
private static String basicAuth(String username, String password) {
return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
}
private HttpResponse<String> post(String url, String path, boolean auth, String taskname) {
logger.debug("posting to " + url + " with path:" + path +", auth: " + auth + " task:" + taskname);
HttpClient.Builder clientBuilder = HttpClient.newBuilder();
HttpClient httpClient = clientBuilder.build();
HttpRequest.Builder builder = HttpRequest.newBuilder();
builder = builder.uri(URI.create(url));
if (auth) {
// do not, DO NOT put authentication here that is not a well-known default already
// DO prompt the user to configure a new password on first authentication
builder = builder.header("Authorization", basicAuth("admin", "admin"));
}
if (path !=null) {
logger.debug("POSTing " + path + " to " + url);
String dashboard = NosqlBenchFiles.readFile(path);
logger.debug("length of content for " + path + " is " + dashboard.length());
builder = builder.POST(HttpRequest.BodyPublishers.ofString(dashboard));
builder.setHeader("Content-Type", "application/json");
} else {
logger.debug(("POSTing empty body to " + url));
builder = builder.POST(HttpRequest.BodyPublishers.noBody());
}
HttpRequest request = builder.build();
try {
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
logger.debug("http response for configuring grafana:\n" + resp);
logger.debug("response status code: " + resp.statusCode());
logger.debug("response body: " + resp.body());
if (resp.statusCode()==412) {
logger.warn("Unable to configure dashboard, grafana precondition failed (status 412): " + resp.body());
String err = "When trying to configure grafana, any errors indicate that you may be trying to RE-configure an instance." +
" This may be a bug. If you already have a docker stack running, you can just use '--report-graphite-to localhost:9109'\n" +
" instead of --docker-metrics.";
throw new BasicError(err);
} else if (resp.statusCode()==401 && resp.body().contains("Invalid username")) {
logger.warn("Unable to configure dashboard, grafana authentication failed (status " + resp.statusCode() + "): " + resp.body());
String err = "Grafana does not have the same password as expected for a new container. We shouldn't be trying to add dashboards on an" +
" existing container. This may be a bug. If you already have a docker stack running, you can just use '--report-graphite-to localhost:9109'" +
" instead of --docker-metrics.";
throw new BasicError(err);
} else if (resp.statusCode()<200 || resp.statusCode()>200) {
logger.error("while trying to " + taskname +", received status code " + resp.statusCode() + " while trying to auto-configure grafana, with body:");
logger.error(resp.body());
throw new RuntimeException("while trying to " + taskname + ", received status code " + resp.statusCode() + " response for " + url + " with body: " + resp.body());
}
return resp;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String startDocker(String IMG, String tag, String name, List<Integer> ports, List<String> volumeDescList, List<String> envList, List<String> cmdList, String reload) {
logger.debug("Starting docker with img=" + IMG + ", tag=" + tag + ", name=" + name + ", " +
"ports=" + ports + ", volumes=" + volumeDescList + ", env=" + envList + ", cmds=" + cmdList + ", reload=" + reload);
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd().withStatusFilter(List.of("exited"));
listContainersCmd.getFilters().put("name", Arrays.asList(name));
List<Container> stoppedContainers = null;
try {
stoppedContainers = listContainersCmd.exec();
for (Container stoppedContainer : stoppedContainers) {
String id = stoppedContainer.getId();
logger.info("Removing exited container: " + id);
dockerClient.removeContainerCmd(id).exec();
}
} catch (Exception e) {
e.printStackTrace();
logger.error("Unable to contact docker, make sure docker is up and try again.");
logger.error("If docker is installed make sure this user has access to the docker group.");
logger.error("$ sudo gpasswd -a ${USER} docker && newgrp docker");
System.exit(1);
}
Container containerId = searchContainer(name, reload);
if (containerId != null) {
return containerId.getId();
}
Info info = dockerClient.infoCmd().exec();
dockerClient.buildImageCmd();
String term = IMG.split("/")[1];
//List<SearchItem> dockerSearch = dockerClient.searchImagesCmd(term).exec();
List<Image> dockerList = dockerClient.listImagesCmd().withImageNameFilter(IMG).exec();
if (dockerList.size() == 0) {
dockerClient.pullImageCmd(IMG)
.withTag(tag)
.exec(new PullImageResultCallback()).awaitSuccess();
dockerList = dockerClient.listImagesCmd().withImageNameFilter(IMG).exec();
if (dockerList.size() == 0) {
logger.error(String.format("Image %s not found, unable to automatically pull image." +
" Check `docker images`",
IMG));
System.exit(1);
}
}
logger.info("Search returned" + dockerList.toString());
List<ExposedPort> tcpPorts = new ArrayList<>();
List<PortBinding> portBindings = new ArrayList<>();
for (Integer port : ports) {
ExposedPort tcpPort = ExposedPort.tcp(port);
Ports.Binding binding = new Ports.Binding("0.0.0.0", String.valueOf(port));
PortBinding pb = new PortBinding(binding, tcpPort);
tcpPorts.add(tcpPort);
portBindings.add(pb);
}
List<Volume> volumeList = new ArrayList<>();
List<Bind> volumeBindList = new ArrayList<>();
for (String volumeDesc : volumeDescList) {
String volFrom = volumeDesc.split(":")[0];
String volTo = volumeDesc.split(":")[1];
Volume vol = new Volume(volTo);
volumeList.add(vol);
volumeBindList.add(new Bind(volFrom, vol));
}
CreateContainerResponse containerResponse;
if (envList == null) {
containerResponse = dockerClient.createContainerCmd(IMG + ":" + tag)
.withCmd(cmdList)
.withExposedPorts(tcpPorts)
.withHostConfig(
new HostConfig()
.withPortBindings(portBindings)
.withPublishAllPorts(true)
.withBinds(volumeBindList)
)
.withName(name)
//.withVolumes(volumeList)
.exec();
} else {
long user = new UnixSystem().getUid();
containerResponse = dockerClient.createContainerCmd(IMG + ":" + tag)
.withEnv(envList)
.withExposedPorts(tcpPorts)
.withHostConfig(
new HostConfig()
.withPortBindings(portBindings)
.withPublishAllPorts(true)
.withBinds(volumeBindList)
)
.withName(name)
.withUser(""+user)
//.withVolumes(volumeList)
.exec();
}
dockerClient.startContainerCmd(containerResponse.getId()).exec();
return containerResponse.getId();
}
private Container searchContainer(String name, String reload) {
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd().withStatusFilter(List.of("running"));
listContainersCmd.getFilters().put("name", Arrays.asList(name));
List<Container> runningContainers = null;
try {
runningContainers = listContainersCmd.exec();
} catch (Exception e) {
e.printStackTrace();
logger.error("Unable to contact docker, make sure docker is up and try again.");
System.exit(1);
}
if (runningContainers.size() >= 1) {
//Container test = runningContainers.get(0);
logger.info(String.format("The container %s is already running", name));
logger.info(String.format("Hupping config"));
if (reload != null) {
post(reload, null, false, "reloading config");
}
return runningContainers.get(0);
}
return null;
}
public void stopMetrics() {
//TODO: maybe implement
}
private class LogCallback extends ResultCallbackTemplate<LogContainerResultCallback, Frame> {
@Override
public void onNext(Frame item) {
if (item.toString().contains("HTTP Server Listen")) {
try {
close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,265 @@
package io.nosqlbench.engine.docker;
/*
*
* @author Sebastián Estévez on 4/4/19.
*
*/
import com.github.dockerjava.api.model.ContainerNetwork;
import com.github.dockerjava.api.model.ContainerNetworkSettings;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.async.ResultCallbackTemplate;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import io.nosqlbench.engine.api.util.NosqlBenchFiles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import static io.nosqlbench.engine.docker.RestHelper.post;
public class DockerMetricsManager {
private final DockerHelper dh;
String userHome = System.getProperty("user.home");
private Logger logger = LoggerFactory.getLogger(DockerMetricsManager.class);
public DockerMetricsManager() {
dh = new DockerHelper();
}
public void startMetrics() {
String ip = startGraphite();
startPrometheus(ip);
startGrafana(ip);
}
private void startGrafana(String ip) {
String GRAFANA_IMG = "grafana/grafana";
String tag = "5.3.2";
String name = "grafana";
List<Integer> port = Arrays.asList(3000);
setupGrafanaFiles(ip);
List<String> volumeDescList = Arrays.asList(
userHome + "/.nosqlbench/grafana:/var/lib/grafana:rw"
//cwd+"/docker-metrics/grafana:/grafana",
//cwd+"/docker-metrics/grafana/datasources:/etc/grafana/provisioning/datasources",
//cwd+"/docker-metrics/grafana/dashboardconf:/etc/grafana/provisioning/dashboards"
//,cwd+"/docker-metrics/grafana/dashboards:/var/lib/grafana/dashboards:ro"
);
List<String> envList = Arrays.asList(
"GF_SECURITY_ADMIN_PASSWORD=admin",
"GF_AUTH_ANONYMOUS_ENABLED=\"true\"",
"GF_SNAPSHOTS_EXTERNAL_SNAPSHOT_URL=https://assethub.datastax.com:3001",
"GF_SNAPSHOTS_EXTERNAL_SNAPSHOT_NAME=\"Upload to DataStax\""
);
String reload = null;
String containerId = dh.startDocker(GRAFANA_IMG, tag, name, port, volumeDescList, envList, null, reload);
if (containerId == null){
return;
}
dh.pollLog(containerId, new LogCallback());
logger.info("grafana container started, http listening");
configureGrafana();
}
private void startPrometheus(String ip) {
logger.info("preparing to start docker metrics");
String PROMETHEUS_IMG = "prom/prometheus";
String tag = "v2.4.3";
String name = "prom";
List<Integer> port = Arrays.asList(9090);
setupPromFiles(ip);
List<String> volumeDescList = Arrays.asList(
//cwd+"/docker-metrics/prometheus:/prometheus",
userHome + "/.nosqlbench/prometheus-conf:/etc/prometheus",
userHome + "/.nosqlbench/prometheus:/prometheus"
//"./prometheus/tg_dse.json:/etc/prometheus/tg_dse.json"
);
List<String> envList = null;
List<String> cmdList = Arrays.asList(
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--storage.tsdb.retention=183d",
"--web.enable-lifecycle"
);
String reload = "http://localhost:9090/-/reload";
dh.startDocker(PROMETHEUS_IMG, tag, name, port, volumeDescList, envList, cmdList, reload);
logger.info("prometheus started and listenning");
}
private String startGraphite() {
logger.info("preparing to start graphite exporter container...");
//docker run -d -p 9108:9108 -p 9109:9109 -p 9109:9109/udp prom/graphite-exporter
String GRAPHITE_EXPORTER_IMG = "prom/graphite-exporter";
String tag = "latest";
String name = "graphite-exporter";
//TODO: look into UDP
List<Integer> port = Arrays.asList(9108, 9109);
List<String> volumeDescList = Arrays.asList();
List<String> envList = Arrays.asList();
String reload = null;
dh.startDocker(GRAPHITE_EXPORTER_IMG, tag, name, port, volumeDescList, envList, null, reload);
logger.info("graphite exporter container started");
logger.info("searching for graphite exporter container ip");
ContainerNetworkSettings settings = dh.searchContainer(name, null).getNetworkSettings();
Map<String, ContainerNetwork> networks = settings.getNetworks();
String ip = null;
for (String key : networks.keySet()) {
ContainerNetwork network = networks.get(key);
ip = network.getIpAddress();
}
return ip;
}
private void setupPromFiles(String ip) {
String datasource = NosqlBenchFiles.readFile("docker/prometheus/prometheus.yml");
if (ip == null) {
logger.error("IP for graphite container not found");
System.exit(1);
}
datasource = datasource.replace("!!!GRAPHITE_IP!!!", ip);
File nosqlbenchDir = new File(userHome, "/.nosqlbench/");
mkdir(nosqlbenchDir);
File prometheusDir = new File(userHome, "/.nosqlbench/prometheus");
mkdir(prometheusDir);
File promConfDir = new File(userHome, "/.nosqlbench/prometheus-conf");
mkdir(promConfDir);
Path prometheusDirPath = Paths.get(userHome, "/.nosqlbench" +
"/prometheus");
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OTHERS_READ);
perms.add(PosixFilePermission.OTHERS_WRITE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
try {
Files.setPosixFilePermissions(prometheusDirPath, perms);
} catch (IOException e) {
logger.error("failed to set permissions on prom backup " +
"directory " + userHome + "/.nosqlbench/prometheus)");
e.printStackTrace();
System.exit(1);
}
try (PrintWriter out = new PrintWriter(
new FileWriter(userHome + "/.nosqlbench/prometheus-conf" +
"/prometheus.yml", false))) {
out.println(datasource);
} catch (FileNotFoundException e) {
e.printStackTrace();
logger.error("error writing prometheus yaml file to ~/.prometheus");
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
logger.error("creating file in ~/.prometheus");
System.exit(1);
}
}
private void mkdir(File dir) {
if(dir.exists()){
return;
}
if(! dir.mkdir()){
if( dir.canWrite()){
System.out.println("no write access");
}
if( dir.canRead()){
System.out.println("no read access");
}
System.out.println("Could not create directory " + dir.getPath());
System.out.println("fix directory permissions to run --docker-metrics");
System.exit(1);
}
}
private void setupGrafanaFiles(String ip) {
File grafanaDir = new File(userHome, "/.nosqlbench/grafana");
mkdir(grafanaDir);
Path grafanaDirPath = Paths.get(userHome, "/.nosqlbench/grafana");
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.OWNER_EXECUTE);
try {
Files.setPosixFilePermissions(grafanaDirPath, perms);
} catch (IOException e) {
logger.error("failed to set permissions on grafana directory " +
"directory " + userHome + "/.nosqlbench/grafana)");
e.printStackTrace();
System.exit(1);
}
}
private void configureGrafana() {
post("http://localhost:3000/api/dashboards/db", "docker/dashboards/analysis.json", true, "load analysis dashboard");
post("http://localhost:3000/api/datasources", "docker/datasources/prometheus-datasource.yaml", true, "configure data source");
}
public void stopMetrics() {
//TODO: maybe implement
}
private class LogCallback extends ResultCallbackTemplate<LogContainerResultCallback, Frame> {
@Override
public void onNext(Frame item) {
if (item.toString().contains("HTTP Server Listen")) {
try {
close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,77 @@
package io.nosqlbench.engine.docker;
import io.nosqlbench.engine.api.exceptions.BasicError;
import io.nosqlbench.engine.api.util.NosqlBenchFiles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
public class RestHelper {
private static Logger logger = LoggerFactory.getLogger(RestHelper.class);
static HttpClient.Builder clientBuilder = HttpClient.newBuilder();
static HttpClient httpClient = clientBuilder.build();
private static String basicAuth(String username, String password) {
return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
}
public static HttpResponse<String> post(String url, String path, boolean auth, String taskname) {
logger.debug("posting to " + url + " with path:" + path +", auth: " + auth + " task:" + taskname);
HttpRequest.Builder builder = HttpRequest.newBuilder();
builder = builder.uri(URI.create(url));
if (auth) {
// do not, DO NOT put authentication here that is not a well-known default already
// DO prompt the user to configure a new password on first authentication
builder = builder.header("Authorization", basicAuth("admin", "admin"));
}
if (path !=null) {
logger.debug("POSTing " + path + " to " + url);
String dashboard = NosqlBenchFiles.readFile(path);
logger.debug("length of content for " + path + " is " + dashboard.length());
builder = builder.POST(HttpRequest.BodyPublishers.ofString(dashboard));
builder.setHeader("Content-Type", "application/json");
} else {
logger.debug(("POSTing empty body to " + url));
builder = builder.POST(HttpRequest.BodyPublishers.noBody());
}
HttpRequest request = builder.build();
try {
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
logger.debug("http response for configuring grafana:\n" + resp);
logger.debug("response status code: " + resp.statusCode());
logger.debug("response body: " + resp.body());
if (resp.statusCode()==412) {
logger.warn("Unable to configure dashboard, grafana precondition failed (status 412): " + resp.body());
String err = "When trying to configure grafana, any errors indicate that you may be trying to RE-configure an instance." +
" This may be a bug. If you already have a docker stack running, you can just use '--report-graphite-to localhost:9109'\n" +
" instead of --docker-metrics.";
throw new BasicError(err);
} else if (resp.statusCode()==401 && resp.body().contains("Invalid username")) {
logger.warn("Unable to configure dashboard, grafana authentication failed (status " + resp.statusCode() + "): " + resp.body());
String err = "Grafana does not have the same password as expected for a new container. We shouldn't be trying to add dashboards on an" +
" existing container. This may be a bug. If you already have a docker stack running, you can just use '--report-graphite-to localhost:9109'" +
" instead of --docker-metrics.";
throw new BasicError(err);
} else if (resp.statusCode()<200 || resp.statusCode()>200) {
logger.error("while trying to " + taskname +", received status code " + resp.statusCode() + " while trying to auto-configure grafana, with body:");
logger.error(resp.body());
throw new RuntimeException("while trying to " + taskname + ", received status code " + resp.statusCode() + " response for " + url + " with body: " + resp.body());
}
return resp;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}