-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[JENKINS-75563] First draft on how to kill a windows container #1724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 10 commits
b504ad1
afd8e2e
a9937b2
b7d02d6
039c3e9
3b60935
1dca89b
9c14c22
d6e1d37
a1387e6
86cfa8a
5dcbc91
c94bccc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,8 +33,10 @@ | |
import io.fabric8.kubernetes.client.dsl.ExecWatch; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.Closeable; | ||
import java.io.FileNotFoundException; | ||
import java.io.FilterOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.InterruptedIOException; | ||
import java.io.OutputStream; | ||
import java.io.PrintStream; | ||
|
@@ -264,6 +266,7 @@ public void setNodeContext(KubernetesNodeContext nodeContext) { | |
this.nodeContext = nodeContext; | ||
} | ||
|
||
private FilePath workspace; | ||
@Override | ||
public Launcher decorate(final Launcher launcher, final Node node) { | ||
|
||
|
@@ -280,6 +283,7 @@ public Proc launch(ProcStarter starter) throws IOException { | |
// find container working dir | ||
KubernetesSlave slave = (KubernetesSlave) node; | ||
FilePath containerWorkingDirFilePath = starter.pwd(); | ||
workspace = containerWorkingDirFilePath; | ||
String containerWorkingDirFilePathStr = containerWorkingDirFilePath != null | ||
? containerWorkingDirFilePath.getRemote() | ||
: ContainerTemplate.DEFAULT_WORKING_DIR; | ||
|
@@ -662,20 +666,38 @@ public void kill(Map<String, String> modelEnvVars) throws IOException, Interrupt | |
getListener().getLogger().println("Killing processes"); | ||
|
||
String cookie = modelEnvVars.get(COOKIE_VAR); | ||
|
||
int exitCode = doLaunch( | ||
true, | ||
int exitCode = 1; | ||
if (this.isUnix()) { | ||
exitCode = doLaunch( | ||
true, | ||
null, | ||
null, | ||
null, | ||
null, | ||
"sh", | ||
"-c", | ||
"kill \\`grep -l '" + COOKIE_VAR + "=" + cookie | ||
+ "' /proc/*/environ | cut -d / -f 3 \\`") | ||
.join(); | ||
} else { | ||
try { | ||
String remote = copyKillScript(workspace, "kill-processes-with-cookie", ".ps1").getRemote(); | ||
String csCode = copyKillScript(workspace, "ProcessEnvironmentReader", ".cs").getRemote(); | ||
exitCode = doLaunch( // Will fail if the script is not present, but it was also failing before in all cases | ||
false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For development, may move to |
||
null, | ||
null, | ||
null, | ||
null, | ||
// TODO Windows | ||
"sh", | ||
"-c", | ||
"kill \\`grep -l '" + COOKIE_VAR + "=" + cookie | ||
+ "' /proc/*/environ | cut -d / -f 3 \\`") | ||
.join(); | ||
|
||
"powershell.exe", "-NoProfile", "-File", | ||
/* path to file may contain spaces so wrap in double quotes*/ | ||
"\""+remote+"\"", | ||
"-cookie", cookie, "-csFile", "\""+csCode+"\"" | ||
).join(); | ||
} catch (Exception e) { | ||
LOGGER.log(Level.FINE, "Exception killing processes", e); | ||
} | ||
} | ||
getListener().getLogger().println("kill finished with exit code " + exitCode); | ||
} | ||
|
||
|
@@ -691,6 +713,24 @@ private void setupEnvironmentVariable(EnvVars vars, PrintStream out, boolean win | |
} | ||
} | ||
} | ||
|
||
private FilePath copyKillScript(FilePath workspace, String scriptName, String scriptSuffix) throws IOException, InterruptedException { | ||
InputStream resourceStream = ContainerExecDecorator.class.getResourceAsStream("scripts/" + scriptName + scriptSuffix); | ||
if (resourceStream == null) { | ||
throw new FileNotFoundException("Script not found in resources!"); | ||
} | ||
FilePath tempFile = workspace.createTempFile(scriptName,scriptSuffix); | ||
try (OutputStream os = tempFile.write()) { | ||
byte[] buffer = new byte[4096]; | ||
int bytesRead; | ||
while ((bytesRead = resourceStream.read(buffer)) != -1) { | ||
os.write(buffer, 0, bytesRead); | ||
} | ||
} finally { | ||
resourceStream.close(); | ||
} | ||
return tempFile; | ||
} | ||
}; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System; | ||
using System.Text; | ||
using System.Diagnostics; | ||
using System.ComponentModel; | ||
using System.Runtime.InteropServices; | ||
|
||
public class ProcessEnvironmentReader | ||
{ | ||
[StructLayout(LayoutKind.Sequential)] | ||
public struct PROCESS_BASIC_INFORMATION | ||
{ | ||
public IntPtr Reserved1; | ||
public IntPtr PebBaseAddress; | ||
public IntPtr Reserved2_0; | ||
public IntPtr Reserved2_1; | ||
public IntPtr UniqueProcessId; | ||
public IntPtr Reserved3; | ||
} | ||
|
||
[DllImport("ntdll.dll")] | ||
private static extern int NtQueryInformationProcess( | ||
IntPtr ProcessHandle, | ||
int ProcessInformationClass, | ||
ref PROCESS_BASIC_INFORMATION ProcessInformation, | ||
uint ProcessInformationLength, | ||
out uint ReturnLength); | ||
|
||
[DllImport("kernel32.dll")] | ||
private static extern IntPtr OpenProcess( | ||
uint dwDesiredAccess, | ||
bool bInheritHandle, | ||
int dwProcessId); | ||
|
||
[DllImport("kernel32.dll")] | ||
private static extern bool ReadProcessMemory( | ||
IntPtr hProcess, | ||
IntPtr lpBaseAddress, | ||
byte[] lpBuffer, | ||
int dwSize, | ||
out IntPtr lpNumberOfBytesRead); | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
static extern bool CloseHandle(IntPtr hHandle); | ||
|
||
private const uint PROCESS_QUERY_INFORMATION = 0x0400; | ||
private const uint PROCESS_VM_READ = 0x0010; | ||
private const int ProcessBasicInformation = 0; | ||
|
||
public static string ReadEnvironmentBlock(int pid) | ||
{ | ||
IntPtr hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); | ||
if (hProcess == IntPtr.Zero) | ||
throw new Win32Exception(Marshal.GetLastWin32Error()); | ||
|
||
try | ||
{ | ||
PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION(); | ||
uint tmp; | ||
int status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, ref pbi, (uint)Marshal.SizeOf(pbi), out tmp); | ||
if (status != 0) | ||
throw new Win32Exception("NtQueryInformationProcess failed"); | ||
|
||
// Offsets for Environment variables are different on 32/64 bit | ||
// The following offsets are for Windows x64 - for x86 some offsets would need adjusting! | ||
// PEB is at pbi.PebBaseAddress | ||
// In PEB, offset 0x20 (Win10 x64, might differ!) is ProcessParameters | ||
byte[] procParamsPtr = new byte[IntPtr.Size]; | ||
IntPtr bytesRead; | ||
IntPtr processParametersAddr; | ||
|
||
// Offset to ProcessParameters | ||
int offsetProcessParameters = 0x20; | ||
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress + offsetProcessParameters, procParamsPtr, procParamsPtr.Length, out bytesRead)) | ||
throw new Win32Exception("ReadProcessMemory (ProcessParameters) failed"); | ||
|
||
processParametersAddr = (IntPtr)BitConverter.ToInt64(procParamsPtr, 0); | ||
|
||
// Offset in RTL_USER_PROCESS_PARAMETERS for Environment = 0x80 (x64)! | ||
int offsetEnvironment = 0x80; | ||
byte[] environmentPtr = new byte[IntPtr.Size]; | ||
|
||
if (!ReadProcessMemory(hProcess, processParametersAddr + offsetEnvironment, environmentPtr, environmentPtr.Length, out bytesRead)) | ||
throw new Win32Exception("ReadProcessMemory (Environment) failed"); | ||
|
||
IntPtr environmentAddr = (IntPtr)BitConverter.ToInt64(environmentPtr, 0); | ||
|
||
// Read an arbitrary chunk (say, 32 KB) where env block should fit | ||
int envSize = 0x8000; | ||
byte[] envData = new byte[envSize]; | ||
if (!ReadProcessMemory(hProcess, environmentAddr, envData, envData.Length, out bytesRead)) | ||
throw new Win32Exception("ReadProcessMemory (Environment data) failed"); | ||
|
||
// Environment block is Unicode, ends with two 0 chars. | ||
string env = Encoding.Unicode.GetString(envData); | ||
int end = env.IndexOf("\0\0"); | ||
|
||
if (end > -1) | ||
env = env.Substring(0, end); | ||
|
||
return env.Replace('\0', '\n'); | ||
} | ||
finally | ||
{ | ||
CloseHandle(hProcess); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
param( | ||
[string]$cookie, | ||
[string]$csFile | ||
) | ||
|
||
Add-Type -Path $csFile | ||
|
||
$failed = $false | ||
Get-Process | ForEach-Object { | ||
$id = $_.Id | ||
try { | ||
$envBlock = [ProcessEnvironmentReader]::ReadEnvironmentBlock($id) | ||
if ($envBlock.Contains("JENKINS_SERVER_COOKIE=$($cookie)")) { | ||
Write-Host "Killing $($_.ProcessName) (ID: $($_.Id)) - JENKINS_SERVER_COOKIE matches" | ||
raul-arabaolaza marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I remove this? It is not gonna be visible to the end user but may help on local runsa nd debugging |
||
try { | ||
Stop-Process -Id $id -Force | ||
Write-Host "Killed." | ||
} catch { | ||
Write-Host "Failed to kill process: $id" | ||
$failed = $true | ||
} | ||
} | ||
} catch { | ||
Write-Error "Failed to read environment variables for $id" | ||
} | ||
|
||
} | ||
if ($failed) { | ||
exit 1 | ||
} else { | ||
exit 0 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,11 @@ spec: | |
''') { | ||
node(POD_LABEL) { | ||
container('shell') { | ||
powershell 'try {Write-Host starting to sleep; Start-Sleep 999999} finally {Write-Host shut down gracefully}' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would only work if a user sends a Ctrl-C to the process, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it would certainly be better for the plugin to emulate the Ctrl-C-like behavior, providing functional parity with the Linux version. If this is simply impossible, then a hard kill is I guess better than the nothing we have now. What happens if the pod is terminated? (gracefully, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I thought something similar yesterday while trying to sleep, I have found some examples of C# code that seems to emulate the ctrl+c that I want to try. I will also try to test the pod deletion. |
||
try { | ||
bat 'echo "starting ping" && ping 127.0.0.1 -n 3601 > test.txt' | ||
} catch (Exception e) { //If killing works we should be able to do things with test.txt | ||
bat 'rename test.txt test2.txt && echo "shut down gracefully"' | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.