package org.arquillian.cube.docker.impl.client.reporter;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.Version;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.layout.mxIGraphLayout;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;
import org.arquillian.cube.docker.impl.client.CubeDockerConfiguration;
import org.arquillian.cube.docker.impl.client.config.CubeContainer;
import org.arquillian.cube.docker.impl.client.config.DockerCompositions;
import org.arquillian.cube.docker.impl.client.config.Link;
import org.arquillian.cube.docker.impl.client.utils.NumberConversion;
import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
import org.arquillian.cube.docker.impl.reporter.DockerContainerSection;
import org.arquillian.cube.spi.CubeRegistry;
import org.arquillian.cube.spi.event.lifecycle.AfterAutoStart;
import org.arquillian.reporter.api.builder.Reporter;
import org.arquillian.reporter.api.builder.report.ReportBuilder;
import org.arquillian.reporter.api.event.SectionEvent;
import org.arquillian.reporter.api.model.entry.FileEntry;
import org.arquillian.reporter.config.ReporterConfiguration;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.spi.event.suite.After;
import org.jboss.arquillian.test.spi.event.suite.Before;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.arquillian.cube.docker.impl.client.reporter.DockerEnvironmentReportKey.*;
/**
* Class that reports generic Docker information like orchestration or docker version.
*/
public class TakeDockerEnvironment {
private static Logger log = Logger.getLogger(TakeDockerEnvironment.class.getName());
private static FileEntry EMPTY_SCREENSHOT = new FileEntry((String) null);
private CubeStatistics statsBeforeMethod;
private CubeStatistics statsAfterMethod;
@Inject
Event<SectionEvent> reportEvent;
public void reportDockerEnvironment(@Observes AfterAutoStart event, CubeDockerConfiguration cubeDockerConfiguration, DockerClientExecutor executor, ReporterConfiguration reporterConfiguration) {
final ReportBuilder reportBuilder = Reporter.createReport(DOCKER_ENVIRONMENT)
.addReport(createDockerInfoGroup(executor));
reportBuilder.addKeyValueEntry(DOCKER_COMPOSITION_SCHEMA, createDockerCompositionSchema(cubeDockerConfiguration, reporterConfiguration));
reportBuilder.addKeyValueEntry(NETWORK_TOPOLOGY_SCHEMA, createNetworkTopologyGraph(cubeDockerConfiguration, executor, reporterConfiguration));
reportBuilder
.inSection(DockerContainerSection.standalone())
.fire(reportEvent);
}
public void captureContainerStatsBeforeTest(@Observes Before before, DockerClientExecutor executor, CubeRegistry cubeRegistry) throws IOException {
captureStats(executor, cubeRegistry, "before", false);
}
public void reportContainerStatsAfterTest(@Observes After after, DockerClientExecutor executor, CubeRegistry cubeRegistry) throws IOException {
captureStats(executor, cubeRegistry, "after", false);
}
public void reportContainerLogs(@Observes org.arquillian.cube.spi.event.lifecycle.BeforeStop beforeStop, DockerClientExecutor executor, ReporterConfiguration reporterConfiguration) throws IOException {
final String cubeId = beforeStop.getCubeId();
if (cubeId != null) {
final File logFile = new File(createContainerLogDirectory(new File(reporterConfiguration.getRootDirectory())), cubeId + ".log");
final Path rootDir = Paths.get(reporterConfiguration.getRootDirectory());
final Path relativePath = rootDir.relativize(logFile.toPath());
executor.copyLog(beforeStop.getCubeId(), false, true, true, true, -1, new FileOutputStream(logFile));
Reporter.createReport()
.addKeyValueEntry(LOG_PATH, new FileEntry(relativePath))
.inSection(new DockerContainerSection(cubeId))
.fire(reportEvent);
}
}
/** private PropertyEntry createContainerStatsIOGroup(Boolean decimal) {
GroupEntry groupEntry = new GroupEntry("IO statistics");
TableEntry tableEntry = new TableEntry();
tableEntry.getTableHead().getRow().addCells(new TableCellEntry("io_bytes_read", 3, 1), new TableCellEntry("io_bytes_write", 3, 1));
tableEntry.getTableBody().addRows(new TableRowEntry(), new TableRowEntry());
addCellsHeader(tableEntry.getTableBody().getRows().get(0), 2);
TableRowEntry tableRowEntry = tableEntry.getTableBody().getRows().get(1);
addCells(tableRowEntry, TakeDockerEnvironment.statsBeforeMethod.getIoBytesRead(),
TakeDockerEnvironment.statsAfterMethod.getIoBytesRead(), decimal);
addCells(tableRowEntry, TakeDockerEnvironment.statsBeforeMethod.getIoBytesWrite(),
TakeDockerEnvironment.statsAfterMethod.getIoBytesWrite(), decimal);
groupEntry.getPropertyEntries().add(tableEntry);
return groupEntry;
}**/
/**private TableEntry createContainerStatMemoryGroup(Boolean decimal) {
TableBuilder tableBuilder = Reporter.createTable(MEMORY_STATISTICS);
tableBuilder.addHeaderRow(USAGE, MAX_USAGE, LIMIT);
tableBuilder.add
TableEntry tableEntry = new TableEntry();
tableEntry.getTableHead().getRow().addCells(new TableCellEntry("usage", 3, 1), new TableCellEntry("max_usage", 3, 1), new TableCellEntry("limit"));
tableEntry.getTableBody().addRows(new TableRowEntry(), new TableRowEntry());
addCellsHeader(tableEntry.getTableBody().getRows().get(0), 2);
TableRowEntry tableRowEntry = tableEntry.getTableBody().getRows().get(1);
addCells(tableRowEntry,TakeDockerEnvironment.statsBeforeMethod.getUsage(),
TakeDockerEnvironment.statsAfterMethod.getUsage(), decimal);
addCells(tableRowEntry, TakeDockerEnvironment.statsBeforeMethod.getMaxUsage(),
TakeDockerEnvironment.statsAfterMethod.getMaxUsage(), decimal);
tableRowEntry.addCell(new TableCellEntry(getHumanReadbale(TakeDockerEnvironment.statsBeforeMethod.getLimit(), decimal)));
groupEntry.getPropertyEntries().add(tableEntry);
return groupEntry;
}**/
/**private PropertyEntry createContainerStatNetworksGroup(Boolean decimal) {
GroupEntry groupEntry = new GroupEntry("Networks statistics");
Map<String, Map<String, Long>> networksBeforeTest = TakeDockerEnvironment.statsBeforeMethod.getNetworks();
Map<String, Map<String, Long>> networksAfterTest = TakeDockerEnvironment.statsBeforeMethod.getNetworks();
TableEntry tableEntry = createTableForNetwork(networksBeforeTest, networksAfterTest, decimal);
groupEntry.getPropertyEntries().add(tableEntry);
return groupEntry;
}
/**private TableEntry createTableForNetwork(Map<String, Map<String, Long>> networksBeforeTest, Map<String, Map<String, Long>> networksAfterTest, Boolean decimal) {
TableEntry tableEntry = new TableEntry();
tableEntry.getTableHead().getRow().addCells(new TableCellEntry("Adapter"), new TableCellEntry("rx_bytes", 3, 1), new TableCellEntry("tx_bytes", 3, 1));
tableEntry.getTableBody().addRow(new TableRowEntry());
tableEntry.getTableBody().getRows().get(0).addCell(new TableCellEntry());
addCellsHeader(tableEntry.getTableBody().getRows().get(0), 2);
for (String adapter : networksBeforeTest.keySet()) {
if (networksBeforeTest.containsKey(adapter) && networksAfterTest.containsKey(adapter)) {
TableRowEntry tableRow = addRowsForNetwork(networksBeforeTest.get(adapter), networksAfterTest.get(adapter), decimal, adapter);
tableEntry.getTableBody().addRow(tableRow);
}
}
return tableEntry;
}**/
/**private TableRowEntry addRowsForNetwork(Map<String, Long> before, Map<String, Long> after, Boolean decimal, String adapter) {
TableRowEntry tableRowEntry = new TableRowEntry();
tableRowEntry.addCell(new TableCellEntry(adapter));
addCells(tableRowEntry, before.get("rx_bytes"), after.get("rx_bytes"), decimal);
addCells(tableRowEntry, before.get("tx_bytes"), after.get("tx_bytes"), decimal);
return tableRowEntry;
}**/
public void captureStats(DockerClientExecutor executor, CubeRegistry cubeRegistry, String when, Boolean decimal) throws IOException {
/**if (executor != null) {
List<Cube<?>> containers = cubeRegistry.getCubes();
for (Cube<?> container : containers) {
String name = container.getId();
Statistics statistics = executor.statsContainer(name);
if ("before".equals(when)) {
this.statsBeforeMethod = updateStats(statistics);
} else {
this.statsAfterMethod = updateStats(statistics);
createEntryAndFire(name, decimal);
}
}
}**/
}
private FileEntry createDockerCompositionSchema(CubeDockerConfiguration cubeDockerConfiguration, ReporterConfiguration reporterConfiguration) {
final mxGraph graph = new mxGraph();
final Object parent = graph.getDefaultParent();
graph.setAutoSizeCells(true);
graph.getModel().beginUpdate();
try {
final DockerCompositions dockerContainersContent = cubeDockerConfiguration.getDockerContainersContent();
final Map<String, CubeContainer> containers = dockerContainersContent.getContainers();
final Map<String, Object> insertedVertex = new HashMap<>();
for (Map.Entry<String, CubeContainer> containerEntry : containers.entrySet()) {
String containerId = containerEntry.getKey();
CubeContainer cubeContainer = containerEntry.getValue();
updateGraph(graph, parent, insertedVertex, containerId, cubeContainer);
}
} finally {
graph.getModel().endUpdate();
}
mxIGraphLayout layout = new mxHierarchicalLayout(graph, SwingConstants.WEST);
layout.execute(graph.getDefaultParent());
return generateCompositionSchemaImage(graph, reporterConfiguration);
}
private FileEntry createNetworkTopologyGraph(CubeDockerConfiguration cubeDockerConfiguration, DockerClientExecutor executor, ReporterConfiguration reporterConfiguration) {
mxGraph graph = new mxGraph();
Object parent = graph.getDefaultParent();
graph.setAutoSizeCells(true);
graph.getModel().beginUpdate();
try {
DockerCompositions dockerCompositions = cubeDockerConfiguration.getDockerContainersContent();
final Map<String, CubeContainer> containers = dockerCompositions.getContainers();
final Map<String, Object> insertedVertex = new HashMap<>();
for (Map.Entry<String, CubeContainer> container: containers.entrySet()) {
final String containerId = container.getKey();
Object containerName = graph.insertVertex(parent, null, containerId, 0, 0, 80, 30);
final CubeContainer cubeContainer = container.getValue();
if (!cubeContainer.isManual()) {
Set<String> nwList = new HashSet<>();
if (cubeContainer.getNetworkMode() != null) {
nwList.add(cubeContainer.getNetworkMode());
} else {
InspectContainerResponse inspect = executor.inspectContainer(containerId);
final String defaultNetwork = inspect.getHostConfig().getNetworkMode();
nwList.add(defaultNetwork);
}
if (cubeContainer.getNetworks() != null) {
nwList.addAll(cubeContainer.getNetworks());
}
for (String nw : nwList) {
Object nwName = null;
if (insertedVertex.containsKey(nw)) {
nwName = insertedVertex.get(nw);
} else {
nwName = graph.insertVertex(parent, null, nw, 0, 0, 60, 20);
graph.setCellStyles(mxConstants.STYLE_FILLCOLOR, "#00FF00", new Object[]{nwName});
}
graph.updateCellSize(nwName);
graph.insertEdge(parent, null, nw, containerName, nwName);
insertedVertex.put(nw, nwName);
}
}
}
} finally {
graph.getModel().endUpdate();
}
mxIGraphLayout layout = new mxHierarchicalLayout(graph, SwingConstants.WEST);
layout.execute(graph.getDefaultParent());
return generateNetworkTopologyImage(graph, reporterConfiguration);
}
private void updateGraph(mxGraph graph, Object parent, Map<String, Object> insertedVertex, String containerId, CubeContainer cubeContainer) {
if (insertedVertex.containsKey(containerId)) {
// container is already added, probably because a direct link from another container
// now we need to add direct links of this one that before were transitive
Object currentContainer = insertedVertex.get(containerId);
createDirectLinks(graph, parent, insertedVertex, cubeContainer, currentContainer);
} else {
// create new cube and possible direct link (not transitive ones)
Object currentContainer = graph.insertVertex(parent, null, containerId, 0, 0, 80, 30);
graph.updateCellSize(currentContainer);
insertedVertex.put(containerId, currentContainer);
createDirectLinks(graph, parent, insertedVertex, cubeContainer, currentContainer);
}
}
private void createDirectLinks(mxGraph graph, Object parent, Map<String, Object> insertedVertex, CubeContainer cubeContainer, Object currentContainer) {
// create relation to all direct links
if (cubeContainer.getLinks() != null) {
for (Link link : cubeContainer.getLinks()) {
final String linkId = link.getName();
Object linkContainer = null;
if (insertedVertex.containsKey(linkId)) {
linkContainer = insertedVertex.get(linkId);
} else {
linkContainer = graph.insertVertex(parent, null, linkId, 0, 0, 80, 30);
}
graph.updateCellSize(currentContainer);
graph.insertEdge(parent, null, link.getAlias(), currentContainer, linkContainer);
insertedVertex.put(linkId, linkContainer);
}
}
}
private FileEntry generateCompositionSchemaImage(mxGraph graph, ReporterConfiguration reporterConfiguration) {
final File imageFile = new File(createSchemasDirectory(new File(reporterConfiguration.getRootDirectory())), "docker_composition.png");
try {
return createScreenshotEntry(imageFile, graph, reporterConfiguration);
} catch (IOException e) {
log.log(Level.WARNING, String.format("Docker compositions schema could not be generated because of %s.", e));
}
return EMPTY_SCREENSHOT;
}
private FileEntry generateNetworkTopologyImage(mxGraph graph, ReporterConfiguration reporterConfiguration) {
try {
final File imageFile = new File(createNetworkTopologyDirectory(new File(reporterConfiguration.getRootDirectory())), "docker_network_topology.png");
return createScreenshotEntry(imageFile, graph, reporterConfiguration);
} catch (IOException e) {
log.log(Level.WARNING, String.format("Docker container network toplogy could not be generated because of %s.", e));
}
return EMPTY_SCREENSHOT;
}
private FileEntry createScreenshotEntry(File imageFile, mxGraph graph, ReporterConfiguration reporterConfiguration) throws IOException {
final BufferedImage bufferedImage = mxCellRenderer.createBufferedImage(graph, null, 1, Color.WHITE, true, null);
ImageIO.write(bufferedImage, "PNG", imageFile);
final Path rootDir = Paths.get(reporterConfiguration.getRootDirectory());
final Path relativize = rootDir.relativize(imageFile.toPath());
return new FileEntry(relativize);
}
private ReportBuilder createDockerInfoGroup(DockerClientExecutor executor) {
Version version = executor.dockerHostVersion();
final ReportBuilder reportBuilder = Reporter.createReport(DOCKER_HOST_INFORMATION)
.addKeyValueEntry(DOCKER_VERSION, version.getVersion())
.addKeyValueEntry(DOCKER_OS, version.getOperatingSystem())
.addKeyValueEntry(DOCKER_KERNEL, version.getKernelVersion())
.addKeyValueEntry(DOCKER_API_VERSION, version.getApiVersion())
.addKeyValueEntry(DOCKER_ARCH, version.getArch());
return reportBuilder;
}
private File createSchemasDirectory(File rootDirectory) {
final Path reportsSchema = Paths.get("reports", "schemas");
final Path schemasDir = rootDirectory.toPath().resolve(reportsSchema);
try {
Files.createDirectories(schemasDir);
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not created schemas directory at %s", schemasDir));
}
return schemasDir.toFile();
}
private File createContainerLogDirectory(File rootDirectory) {
final Path reportsLogs = Paths.get("reports", "logs");
final Path logsDir = rootDirectory.toPath().resolve(reportsLogs);
if (Files.notExists(logsDir)) {
try {
Files.createDirectories(logsDir);
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not created logs directory at %s", logsDir));
}
}
return logsDir.toFile();
}
private File createNetworkTopologyDirectory(File rootDirectory) {
final Path reportsNetworks = Paths.get("reports", "networks");
final Path networksDir = rootDirectory.toPath().resolve(reportsNetworks);
try {
Files.createDirectories(networksDir);
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not created networks directory at %s", networksDir));
}
return networksDir.toFile();
}
private String getHumanReadbale(Long bytes, Boolean decimal) {
return NumberConversion.humanReadableByteCount(bytes, decimal);
}
/**private void addCellsHeader(TableRowEntry tableRowEntry, Integer params) {
for (int i = 0; i < params; i++) {
tableRowEntry.addCells(new TableCellEntry("Before Test"), new TableCellEntry("After Test"), new TableCellEntry("Use"));
}
}
private void addCells(TableRowEntry tableRowEntry, Long beforeTest, Long afterTest, Boolean decimal) {
tableRowEntry.addCells(
new TableCellEntry(getHumanReadbale(beforeTest, decimal)),
new TableCellEntry(getHumanReadbale(afterTest, decimal)),
new TableCellEntry(getHumanReadbale((afterTest - beforeTest), decimal)));
}**/
}