/** * Reads file content by specified file path. * * <p>TODO: add file size checking, note that size checking and file content reading should be * done in an atomic way, which means that two separate instance processes is not the case. * * @param filePath path to file on machine instance * @param startFrom line number to start reading from * @param limit limitation on line * @return if {@code limit} and {@code startFrom} grater than 0 content from {@code startFrom} to * {@code startFrom + limit} will be returned, if file contains less lines than {@code * startFrom} empty content will be returned * @throws MachineException if any error occurs with file reading */ @Override public String readFileContent(String filePath, int startFrom, int limit) throws MachineException { if (limit <= 0 || startFrom <= 0) { throw new MachineException( "Impossible to read file " + limit + " lines from " + startFrom + " line"); } // command sed getting file content from startFrom line to (startFrom + limit) String bashCommand = format("sed -n \'%1$2s, %2$2sp\' %3$2s", startFrom, startFrom + limit, filePath); final String[] command = {"/bin/bash", "-c", bashCommand}; ListLineConsumer lines = new ListLineConsumer(); try { Exec exec = docker.createExec(container, false, command); docker.startExec(exec.getId(), new LogMessagePrinter(lines, LogMessage::getContent)); } catch (IOException e) { throw new MachineException( format( "Error occurs while initializing command %s in docker container %s: %s", Arrays.toString(command), container, e.getLocalizedMessage()), e); } String content = lines.getText(); if (content.contains("sed: can't read " + filePath + ": No such file or directory") || content.contains("cat: " + filePath + ": No such file or directory")) { throw new MachineException("File with path " + filePath + " not found"); } return content; }
@Override public InstanceKey saveToSnapshot(String owner) throws MachineException { try { final String repository = generateRepository(); String comment = format("Suspended at %1$ta %1$tb %1$td %1$tT %1$tZ %1$tY", System.currentTimeMillis()); if (owner != null) { comment = comment + " by " + owner; } // !! We SHOULD NOT pause container before commit because all execs will fail // to push image to private registry it should be tagged with registry in repo name // https://docs.docker.com/reference/api/docker_remote_api_v1.16/#push-an-image-on-the-registry docker.commit(container, registry + "/" + repository, null, comment, owner); // TODO fix this workaround. Docker image is not visible after commit when using swarm Thread.sleep(2000); final ProgressLineFormatterImpl progressLineFormatter = new ProgressLineFormatterImpl(); docker.push( repository, null, registry, currentProgressStatus -> { try { outputConsumer.writeLine(progressLineFormatter.format(currentProgressStatus)); } catch (IOException ignored) { } }); return new DockerInstanceKey(repository, registry); } catch (IOException e) { throw new MachineException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MachineException(e.getLocalizedMessage(), e); } }
@Override public List<InstanceProcess> getProcesses() throws MachineException { List<InstanceProcess> processes = new LinkedList<>(); try { final Exec exec = docker.createExec(container, false, "/bin/bash", "-c", GET_ALIVE_PROCESSES_COMMAND); docker.startExec( exec.getId(), logMessage -> { final String pidFilePath = logMessage.getContent().trim(); final Matcher matcher = PID_FILE_PATH_PATTERN.matcher(pidFilePath); if (matcher.matches()) { final int virtualPid = Integer.parseInt(matcher.group(1)); final InstanceProcess dockerProcess = machineProcesses.get(virtualPid); if (dockerProcess != null) { processes.add(dockerProcess); } else { LOG.warn( "Machine process {} exists in container but missing in processes map", virtualPid); } } }); return processes; } catch (IOException e) { throw new MachineException(e); } }
/** * Copies files from specified container. * * @param sourceMachine source machine * @param sourcePath path to file or directory inside specified container * @param targetPath path to destination file or directory inside container * @param overwrite If "false" then it will be an error if unpacking the given content would cause * an existing directory to be replaced with a non-directory and vice versa. * @throws MachineException if any error occurs when files are being copied */ @Override public void copy(Instance sourceMachine, String sourcePath, String targetPath, boolean overwrite) throws MachineException { if (!(sourceMachine instanceof DockerInstance)) { throw new MachineException("Unsupported copying between not docker machines"); } try { docker.putResource( container, targetPath, docker.getResource(((DockerInstance) sourceMachine).container, sourcePath), overwrite); } catch (IOException e) { throw new MachineException(e.getLocalizedMessage()); } }
@Override public void destroy() throws MachineException { machineProcesses.clear(); processesCleaner.untrackProcesses(getId()); dockerInstanceStopDetector.stopDetection(container); try { if (getConfig().isDev()) { node.unbindWorkspace(); } docker.killContainer(container); docker.removeContainer(container, true, true); } catch (IOException e) { throw new MachineException(e.getLocalizedMessage()); } try { docker.removeImage(image, false); } catch (IOException ignore) { } }
@Override public MachineRuntimeInfoImpl getRuntime() { if (machineRuntime == null) { try { final ContainerInfo containerInfo = docker.inspectContainer(container); machineRuntime = new MachineRuntimeInfoImpl( dockerMachineFactory.createMetadata(containerInfo, node.getHost())); } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); return null; } } return machineRuntime; }