/** * Copy some files by executing a tar command to the stdout and return the InputStream that contains the tar * contents. * <p> * Unfortunately due to the fact that any error handling happens on another thread, if the tar command fails the * InputStream will simply be empty and it will close. It won't propagate an Exception to the reader of the * InputStream. */ @Override public InputStream copyFiles(String containerId, String folderName) { ByteArrayOutputStream stderr = new ByteArrayOutputStream(); String[] command = new String[] { "tar", "-C", folderName, "-c", "." }; CopyFilesExecListener listener = new CopyFilesExecListener(stderr, command, containerId); ExecWatch exec = client.pods().withName(containerId).redirectingOutput().writingError(stderr).usingListener(listener).exec(command); // FIXME: This is a bit dodgy, but we need the listener to be able to close the ExecWatch in failure conditions, // because it doesn't cleanup properly and deadlocks. // Needs bugs fixed inside kubernetes-client. listener.setExecWatch(exec); // When zalenium is under high load sometimes the stdout isn't connected by the time we try to read from it. // Let's wait until it is connected before proceeding. listener.waitForInputStreamToConnect(); return exec.getOutput(); }
@Override public void onClose(int code, String reason) { // Dirty hack to workaround the fact that ExecWatch doesn't automatically close any resources boolean isClosed = closedResource.getAndSet(true); boolean hasErrors = stderr.size() > 0; if (!isClosed && hasErrors) { logger.error(String.format("%s Copy files command failed with:\n\tcommand: %s\n\t stderr:\n%s", containerId, Arrays.toString(command), stderr.toString())); this.execWatch.close(); } }
@Override public OutputStream getStdin() { return watch.getInput(); } }
private void openReaderThreads(Session session, SessionExecModel sessionExecModel) { // TODO: if there is a leak, this might be a suspect.. close those? sessionExecModel.getExecutor().execute(() -> WebsocketWriter.readCharsFromStreamToSession(sessionExecModel.getExecWatch().getOutput(), session)); sessionExecModel.getExecutor().execute(() -> WebsocketWriter.readCharsFromStreamToSession(sessionExecModel.getExecWatch().getError(), session)); } }
@Override public InputStream getStderr() { return watch.getError(); }
@Override public OutputStream getStdin() { return watch.getInput(); }
@Override public void kill() throws IOException, InterruptedException { //What we actually do is send a ctrl-c to the current process and then exit the shell. watch.getInput().write(CTRL_C); watch.getInput().write(EXIT.getBytes(UTF_8)); watch.getInput().write(NEWLINE.getBytes(UTF_8)); watch.getInput().flush(); }
@Override public InputStream getStdout() { return watch.getOutput(); }
logger.error(String.format("%s Failed to execute command %s", containerId, Arrays.toString(command)), e); } finally { exec.close();
@Override public void kill() throws IOException, InterruptedException { try { // What we actually do is send a ctrl-c to the current process and then exit the shell. watch.getInput().write(CTRL_C); watch.getInput().write(EXIT.getBytes(StandardCharsets.UTF_8)); watch.getInput().write(NEWLINE.getBytes(StandardCharsets.UTF_8)); watch.getInput().flush(); } catch (IOException e) { LOGGER.log(Level.FINE, "Proc kill failed, ignoring", e); } finally { close(); } }
@Override public InputStream getStdout() { return watch.getOutput(); }
@Override public void close() throws IOException { try { // We are calling explicitly close, in order to cleanup websockets and threads (are not closed implicitly). watch.close(); } catch (Exception e) { LOGGER.log(Level.INFO, "failed to close watch", e); } } }
private void setupEnvironmentVariable(EnvVars vars, ExecWatch watch) throws IOException { for (Map.Entry<String, String> entry : vars.entrySet()) { //Check that key is bash compliant. if (entry.getKey().matches("[a-zA-Z_][a-zA-Z0-9_]*")) { watch.getInput().write( String.format( "export %s='%s'%s", entry.getKey(), entry.getValue().replace("'", "'\\''"), NEWLINE ).getBytes(StandardCharsets.UTF_8) ); } } }
@Override public InputStream getStderr() { return watch.getOutput(); }
private static void closeWatch(ExecWatch watch) { try { watch.close(); } catch (Exception e) { LOGGER.log(Level.INFO, "failed to close watch", e); } }
out.print(s); watch.getInput().write(s.getBytes(StandardCharsets.UTF_8)); watch.getInput().write(NEWLINE.getBytes(StandardCharsets.UTF_8)); out.print(EXIT + NEWLINE); LOGGER.log(Level.FINEST, "Executing command: {0}", sb); watch.getInput().write((EXIT + NEWLINE).getBytes(StandardCharsets.UTF_8)); watch.getInput().flush(); } catch (IOException e) { e.printStackTrace(out);
@OnClose public void onClose(Session session) { logger.info("Closing session.."); SessionExecModel sessionExecModel = execWebSocketSessionStore.getSessionExecModel(session); sessionExecModel.getExecutor().shutdownNow(); sessionExecModel.getExecWatch().close(); execWebSocketSessionStore.deleteSession(session); }
@OnMessage public void onMessageReceived(Session session, String command) { SessionExecModel sessionModel = execWebSocketSessionStore.getSessionExecModel(session); ExecWatch execWatch = sessionModel.getExecWatch(); if (execWatch == null) { logger.info("Got message to an unknown ExecWatch, ignoring!"); return; } try { execWatch.getInput().write(command.getBytes()); } catch (IOException e) { logger.warn("Got IO Exception while writing to the ExecWatch!", e); } }