use of org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave in project kubernetes-plugin by jenkinsci.
the class RestartPipelineTest method taskListenerAfterRestart.
@Test
public void taskListenerAfterRestart() {
AtomicReference<String> projectName = new AtomicReference<>();
story.then(r -> {
configureAgentListener();
configureCloud();
WorkflowRun b = getPipelineJobThenScheduleRun(r);
projectName.set(b.getParent().getFullName());
r.waitForMessage("+ sleep", b);
});
story.then(r -> {
WorkflowRun b = r.jenkins.getItemByFullName(projectName.get(), WorkflowJob.class).getBuildByNumber(1);
Optional<Node> first = r.jenkins.getNodes().stream().filter(KubernetesSlave.class::isInstance).findFirst();
assertTrue("Kubernetes node should be present after restart", first.isPresent());
KubernetesSlave node = (KubernetesSlave) first.get();
r.waitForMessage("Ready to run", b);
waitForTemplate(node).getListener().getLogger().println("This got printed");
r.waitForMessage("This got printed", b);
b.getExecutor().interrupt();
r.assertBuildStatus(Result.ABORTED, r.waitForCompletion(b));
});
}
use of org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave in project kubernetes-plugin by jenkinsci.
the class Reaper method activate.
private void activate() {
LOGGER.fine("Activating reaper");
// or pods may have been deleted before any Kubernetes agent was brought online.)
for (Node n : new ArrayList<>(Jenkins.get().getNodes())) {
if (!(n instanceof KubernetesSlave)) {
continue;
}
KubernetesSlave ks = (KubernetesSlave) n;
if (ks.getLauncher().isLaunchSupported()) {
// Being launched, don't touch it.
continue;
}
String ns = ks.getNamespace();
String name = ks.getPodName();
try {
// then go back and individually check any unmatched agents with their configured namespace.
if (ks.getKubernetesCloud().connect().pods().inNamespace(ns).withName(name).get() == null) {
LOGGER.info(() -> ns + "/" + name + " seems to have been deleted, so removing corresponding Jenkins agent");
Jenkins.get().removeNode(ks);
} else {
LOGGER.fine(() -> ns + "/" + name + " still seems to exist, OK");
}
} catch (Exception x) {
LOGGER.log(Level.WARNING, "failed to do initial reap check for " + ns + "/" + name, x);
}
}
// Now set up a watch for any subsequent pod deletions.
for (Cloud c : Jenkins.get().clouds) {
if (!(c instanceof KubernetesCloud)) {
continue;
}
KubernetesCloud kc = (KubernetesCloud) c;
try {
KubernetesClient client = kc.connect();
watch = client.pods().inNamespace(client.getNamespace()).watch(this);
} catch (Exception x) {
LOGGER.log(Level.WARNING, "failed to set up watcher on " + kc.getDisplayName(), x);
}
}
}
use of org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave in project kubernetes-plugin by jenkinsci.
the class ContainerExecDecorator method decorate.
@Override
public Launcher decorate(final Launcher launcher, final Node node) {
// If the node is not a KubernetesSlave return the original launcher
if (node != null && !(node instanceof KubernetesSlave)) {
return launcher;
}
return new Launcher.DecoratedLauncher(launcher) {
@Override
public Proc launch(ProcStarter starter) throws IOException {
LOGGER.log(Level.FINEST, "Launch proc with environment: {0}", Arrays.toString(starter.envs()));
// find container working dir
KubernetesSlave slave = (KubernetesSlave) node;
FilePath containerWorkingDirFilePath = starter.pwd();
String containerWorkingDirFilePathStr = containerWorkingDirFilePath != null ? containerWorkingDirFilePath.getRemote() : ContainerTemplate.DEFAULT_WORKING_DIR;
String containerWorkingDirStr = ContainerTemplate.DEFAULT_WORKING_DIR;
if (slave != null && slave.getPod().isPresent() && containerName != null) {
Optional<Container> container = slave.getPod().get().getSpec().getContainers().stream().filter(container1 -> container1.getName().equals(containerName)).findAny();
Optional<String> containerWorkingDir = Optional.empty();
if (container.isPresent() && container.get().getWorkingDir() != null) {
containerWorkingDir = Optional.of(container.get().getWorkingDir());
}
if (containerWorkingDir.isPresent()) {
containerWorkingDirStr = containerWorkingDir.get();
}
if (containerWorkingDir.isPresent() && containerWorkingDirFilePath != null && !containerWorkingDirFilePath.getRemote().startsWith(containerWorkingDirStr)) {
// Container has a custom workingDir, updated the pwd to match container working dir
containerWorkingDirFilePathStr = containerWorkingDirFilePath.getRemote().replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
containerWorkingDirFilePath = new FilePath(containerWorkingDirFilePath.getChannel(), containerWorkingDirFilePathStr);
LOGGER.log(Level.FINEST, "Modified the pwd to match {0} containers workspace directory : {1}", new String[] { containerName, containerWorkingDirFilePathStr });
}
}
String[] envVars = starter.envs();
// modify the working dir on envvars part of starter env vars
if (!containerWorkingDirStr.equals(ContainerTemplate.DEFAULT_WORKING_DIR)) {
for (int i = 0; i < envVars.length; i++) {
String keyValue = envVars[i];
String[] split = keyValue.split("=", 2);
if (split[1].startsWith(ContainerTemplate.DEFAULT_WORKING_DIR)) {
// Container has a custom workingDir, update env vars with right workspace folder
split[1] = split[1].replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
envVars[i] = split[0] + "=" + split[1];
LOGGER.log(Level.FINEST, "Updated the starter environment variable, key: {0}, Value: {1}", new String[] { split[0], split[1] });
}
}
}
if (node != null) {
// It seems this is possible despite the method javadoc saying it is non-null
final Computer computer = node.toComputer();
if (computer != null) {
List<String> resultEnvVar = new ArrayList<>();
try {
EnvVars environment = computer.getEnvironment();
if (environment != null) {
Set<String> overriddenKeys = new HashSet<>();
for (String keyValue : envVars) {
String[] split = keyValue.split("=", 2);
if (!split[1].equals(environment.get(split[0]))) {
// Only keep environment variables that differ from Computer's environment
resultEnvVar.add(keyValue);
overriddenKeys.add(split[0]);
}
}
// modify the working dir on envvars part of Computer
if (!containerWorkingDirStr.equals(ContainerTemplate.DEFAULT_WORKING_DIR)) {
for (Map.Entry<String, String> entry : environment.entrySet()) {
if (entry.getValue().startsWith(ContainerTemplate.DEFAULT_WORKING_DIR) && !overriddenKeys.contains(entry.getKey())) {
// Value should be overridden and is not overridden earlier
String newValue = entry.getValue().replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
String keyValue = entry.getKey() + "=" + newValue;
LOGGER.log(Level.FINEST, "Updated the value for envVar, key: {0}, Value: {1}", new String[] { entry.getKey(), newValue });
resultEnvVar.add(keyValue);
}
}
}
envVars = resultEnvVar.toArray(new String[resultEnvVar.size()]);
}
} catch (InterruptedException e) {
throw new IOException("Unable to retrieve environment variables", e);
}
}
}
return doLaunch(starter.quiet(), fixDoubleDollar(envVars), starter.stdout(), containerWorkingDirFilePath, starter.masks(), getCommands(starter, containerWorkingDirFilePathStr, launcher.isUnix()));
}
private Proc doLaunch(boolean quiet, String[] cmdEnvs, OutputStream outputForCaller, FilePath pwd, boolean[] masks, String... commands) throws IOException {
final CountDownLatch started = new CountDownLatch(1);
final CountDownLatch finished = new CountDownLatch(1);
final AtomicBoolean alive = new AtomicBoolean(false);
final AtomicLong startAlive = new AtomicLong();
long startMethod = System.nanoTime();
PrintStream printStream;
OutputStream stream;
// Only output to stdout at the beginning for diagnostics.
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
// Wrap stdout so that we can toggle it off.
ToggleOutputStream toggleStdout = new ToggleOutputStream(stdout);
// Do not send this command to the output when in quiet mode
if (quiet) {
stream = toggleStdout;
printStream = new PrintStream(stream, true, StandardCharsets.UTF_8.toString());
} else {
printStream = launcher.getListener().getLogger();
stream = new TeeOutputStream(toggleStdout, printStream);
}
ByteArrayOutputStream dryRunCaller = null;
;
ToggleOutputStream toggleDryRunCaller = null;
ToggleOutputStream toggleOutputForCaller = null;
// Send to proc caller as well if they sent one
if (outputForCaller != null && !outputForCaller.equals(printStream)) {
if (launcher.isUnix()) {
stream = new TeeOutputStream(outputForCaller, stream);
} else {
// Prepare to capture output for later.
dryRunCaller = new ByteArrayOutputStream();
toggleDryRunCaller = new ToggleOutputStream(dryRunCaller);
// Initially disable the output for the caller, to prevent it from getting unwanted output such as prompt
toggleOutputForCaller = new ToggleOutputStream(outputForCaller, true);
stream = new TeeOutputStream(toggleOutputForCaller, stream);
stream = new TeeOutputStream(toggleDryRunCaller, stream);
}
}
ByteArrayOutputStream error = new ByteArrayOutputStream();
String[] sh = shell != null ? new String[] { shell } : launcher.isUnix() ? new String[] { "sh" } : new String[] { "cmd", "/Q" };
String msg = "Executing " + String.join(" ", sh) + " script inside container " + containerName + " of pod " + getPodName();
LOGGER.log(Level.FINEST, msg);
printStream.println(msg);
if (closables == null) {
closables = new ArrayList<>();
}
Execable<String, ExecWatch> execable = //
getClient().pods().inNamespace(getNamespace()).withName(getPodName()).inContainer(containerName).redirectingInput(// JENKINS-50429
STDIN_BUFFER_SIZE).writingOutput(stream).writingError(stream).writingErrorChannel(error).usingListener(new ExecListener() {
@Override
public void onOpen() {
alive.set(true);
started.countDown();
startAlive.set(System.nanoTime());
LOGGER.log(Level.FINEST, "onOpen : {0}", finished);
}
@Override
public void onFailure(Throwable t, Response response) {
alive.set(false);
t.printStackTrace(launcher.getListener().getLogger());
started.countDown();
LOGGER.log(Level.FINEST, "onFailure : {0}", finished);
if (finished.getCount() == 0) {
LOGGER.log(Level.WARNING, "onFailure called but latch already finished. This may be a bug in the kubernetes-plugin");
}
finished.countDown();
}
@Override
public void onClose(int i, String s) {
alive.set(false);
started.countDown();
LOGGER.log(Level.FINEST, "onClose : {0} [{1} ms]", new Object[] { finished, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startAlive.get()) });
if (finished.getCount() == 0) {
LOGGER.log(Level.WARNING, "onClose called but latch already finished. This indicates a bug in the kubernetes-plugin");
}
finished.countDown();
}
});
ExecWatch watch;
try {
watch = execable.exec(sh);
} catch (KubernetesClientException e) {
if (e.getCause() instanceof InterruptedException) {
throw new IOException("Interrupted while starting websocket connection, you should increase the Max connections to Kubernetes API", e);
} else {
throw e;
}
} catch (RejectedExecutionException e) {
throw new IOException("Connection was rejected, you should increase the Max connections to Kubernetes API", e);
}
boolean hasStarted = false;
try {
// prevent a wait forever if the connection is closed as the listener would never be called
hasStarted = started.await(WEBSOCKET_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
closeWatch(watch);
throw new IOException("Interrupted while waiting for websocket connection, you should increase the Max connections to Kubernetes API", e);
}
if (!hasStarted) {
closeWatch(watch);
throw new IOException("Timed out waiting for websocket connection. " + "You should increase the value of system property " + WEBSOCKET_CONNECTION_TIMEOUT_SYSTEM_PROPERTY + " currently set at " + WEBSOCKET_CONNECTION_TIMEOUT + " seconds");
}
try {
// Not fully satisfied with this solution because it can delay the execution
if (finished.await(COMMAND_FINISHED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
launcher.getListener().error("Process exited immediately after creation. See output below%n%s", stdout.toString(StandardCharsets.UTF_8.name()));
throw new AbortException("Process exited immediately after creation. Check logs above for more details.");
}
toggleStdout.disable();
OutputStream stdin = watch.getInput();
PrintStream in = new PrintStream(stdin, true, StandardCharsets.UTF_8.name());
if (!launcher.isUnix()) {
in.print("@echo off");
in.print(newLine(true));
}
if (pwd != null) {
// We need to get into the project workspace.
// The workspace is not known in advance, so we have to execute a cd command.
in.print(String.format("cd \"%s\"", pwd));
in.print(newLine(!launcher.isUnix()));
}
EnvVars envVars = new EnvVars();
// get global vars here, run the export first as they'll get overwritten.
if (globalVars != null) {
envVars.overrideAll(globalVars);
}
if (rcEnvVars != null) {
envVars.overrideAll(rcEnvVars);
}
if (environmentExpander != null) {
environmentExpander.expand(envVars);
}
// setup specific command envs passed into cmd
if (cmdEnvs != null) {
for (String cmdEnv : cmdEnvs) {
envVars.addLine(cmdEnv);
}
}
LOGGER.log(Level.FINEST, "Launching with env vars: {0}", envVars.toString());
setupEnvironmentVariable(envVars, in, !launcher.isUnix());
if (!launcher.isUnix() && toggleOutputForCaller != null) {
// Windows welcome message should not be sent to the caller as it is a side-effect of calling the wrapping cmd.exe
// Microsoft Windows [Version 10.0.17763.2686]
// (c) 2018 Microsoft Corporation. All rights reserved.
//
// C:\>
stream.flush();
long beginning = System.currentTimeMillis();
// watch for the prompt character
while (!dryRunCaller.toString(StandardCharsets.UTF_8.name()).contains(">")) {
Thread.sleep(100);
}
LOGGER.log(Level.FINEST, "Windows prompt printed after " + (System.currentTimeMillis() - beginning) + " ms");
}
// We don't need to capture output anymore
if (toggleDryRunCaller != null) {
toggleDryRunCaller.disable();
}
// Clear any captured bytes
if (dryRunCaller != null) {
dryRunCaller.reset();
}
if (toggleOutputForCaller != null) {
toggleOutputForCaller.enable();
}
doExec(in, !launcher.isUnix(), printStream, masks, commands);
LOGGER.log(Level.INFO, "Created process inside pod: [" + getPodName() + "], container: [" + containerName + "]" + "[" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startMethod) + " ms]");
ContainerExecProc proc = new ContainerExecProc(watch, alive, finished, stdin, error);
closables.add(proc);
return proc;
} catch (InterruptedException ie) {
throw new InterruptedIOException(ie.getMessage());
} catch (Exception e) {
closeWatch(watch);
throw e;
}
}
@Override
public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
getListener().getLogger().println("Killing processes");
String cookie = modelEnvVars.get(COOKIE_VAR);
int exitCode = doLaunch(true, null, null, null, null, // TODO Windows
"sh", "-c", "kill \\`grep -l '" + COOKIE_VAR + "=" + cookie + "' /proc/*/environ | cut -d / -f 3 \\`").join();
getListener().getLogger().println("kill finished with exit code " + exitCode);
}
private void setupEnvironmentVariable(EnvVars vars, PrintStream out, boolean windows) 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_]*")) {
out.print(String.format(windows ? "set %s=%s" : "export %s='%s'", entry.getKey(), windows ? entry.getValue() : entry.getValue().replace("'", "'\\''")));
out.print(newLine(windows));
}
}
}
};
}
use of org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave in project kubernetes-plugin by jenkinsci.
the class Reaper method eventReceived.
@Override
public void eventReceived(Watcher.Action action, Pod pod) {
String ns = pod.getMetadata().getNamespace();
String name = pod.getMetadata().getName();
Jenkins jenkins = Jenkins.getInstanceOrNull();
if (jenkins == null) {
return;
}
Optional<KubernetesSlave> optionalNode = resolveNode(jenkins, ns, name);
if (!optionalNode.isPresent()) {
return;
}
ExtensionList.lookup(Listener.class).forEach(listener -> {
try {
listener.onEvent(action, optionalNode.get(), pod);
} catch (Exception x) {
LOGGER.log(Level.WARNING, "Listener " + listener + " failed for " + ns + "/" + name, x);
}
});
}
use of org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave in project kubernetes-plugin by jenkinsci.
the class KubernetesPipelineTest method computerCantBeConfigured.
@Test
@Ignore
public void computerCantBeConfigured() throws Exception {
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
SemaphoreStep.waitForStart("pod/1", b);
Optional<KubernetesSlave> optionalNode = r.jenkins.getNodes().stream().filter(KubernetesSlave.class::isInstance).map(KubernetesSlave.class::cast).findAny();
assertTrue(optionalNode.isPresent());
KubernetesSlave node = optionalNode.get();
JenkinsRule.WebClient wc = r.createWebClient();
wc.getOptions().setPrintContentOnFailingStatusCode(false);
wc.login("admin");
HtmlPage nodeIndex = wc.getPage(node);
assertNotXPath(nodeIndex, "//*[text() = 'configure']");
if (Jenkins.get().getVersion().isNewerThanOrEqualTo(new VersionNumber("2.238"))) {
r.assertXPath(nodeIndex, "//*[text() = 'View Configuration']");
} else {
wc.assertFails(node.toComputer().getUrl() + "configure", 403);
}
SemaphoreStep.success("pod/1", null);
}
Aggregations