Added shutdownOnIdle option , also simplified configuration for windows slave. Now default script works although jenkins is configured for security

This commit is contained in:
snallami 2014-12-05 21:06:33 -08:00
parent a8a74a1757
commit 643383f4d3
11 changed files with 198 additions and 71 deletions

View File

@ -7,7 +7,7 @@
</parent>
<artifactId>azure-slave-plugin</artifactId>
<version>0.2.2-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>
<packaging>hpi</packaging>
<name>Azure Slave Plugin</name>
<description>Provisions slave on Azure cloud</description>

View File

@ -28,6 +28,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.slaves.JnlpSlaveAgentProtocol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
@ -195,6 +198,31 @@ public class AzureCloud extends Cloud {
Computer.threadPoolForRemoting.submit(new Callable<Node>() {
public Node call() throws Exception {
// Verify if there are any shutdown(deallocated) nodes that can be reused.
for (Computer slaveComputer : Jenkins.getInstance().getComputers()) {
if (slaveComputer instanceof AzureComputer && slaveComputer.isOffline()) {
AzureComputer azureComputer = (AzureComputer)slaveComputer;
AzureSlave slaveNode = azureComputer.getNode();
LOGGER.info("Azure Cloud: provision: "+slaveNode.getLabelString());
if (!slaveNode.isDeleteSlave() && slaveNode.getLabelString().contains(slaveTemplate.getLabels())) {
try {
AzureManagementServiceDelegate.startVirtualMachine(slaveNode);
//waitUntilOnline(slaveNode);
Hudson.getInstance().addNode(slaveNode);
slaveNode.toComputer().connect(false).get();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@SuppressWarnings("deprecation")
AzureSlave slave = slaveTemplate.provisionSlave(new StreamTaskListener(System.out));
// Get virtual machine properties
@ -203,15 +231,15 @@ public class AzureCloud extends Cloud {
slaveTemplate.setVirtualMachineDetails(slave);
try {
if (slave.getSlaveLaunchMethod().equalsIgnoreCase("SSH")) {
slaveTemplate.waitForReadyRole(slave);
LOGGER.info("Azure Cloud: provision: Waiting for ssh server to comeup");
Thread.sleep(2 * 60 * 1000);
// slaveTemplate.waitForReadyRole(slave);
// LOGGER.info("Azure Cloud: provision: Waiting for ssh server to comeup");
// Thread.sleep(2 * 60 * 1000);
LOGGER.info("Azure Cloud: provision: Adding slave to azure nodes ");
Hudson.getInstance().addNode(slave);
slave.toComputer().connect(false).get();
} else if (slave.getSlaveLaunchMethod().equalsIgnoreCase("JNLP")) {
LOGGER.info("Azure Cloud: provision: Checking for slave status");
slaveTemplate.waitForReadyRole(slave);
// slaveTemplate.waitForReadyRole(slave);
LOGGER.info("Azure Cloud: provision: Adding slave to azure nodes ");
Hudson.getInstance().addNode(slave);

View File

@ -50,6 +50,8 @@ import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import jenkins.slaves.JnlpSlaveAgentProtocol;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.xml.sax.SAXException;
@ -331,6 +333,8 @@ public class AzureManagementServiceDelegate {
String fileName = cloudServiceName+roleName+"initscript.ps1";
String initScript = null;
String jnlpSecret = JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(roleName);
if (AzureUtil.isNull(template.getInitScript())) {
// Move this to a file
initScript = AzureUtil.DEFAULT_INIT_SCRIPT;
@ -349,7 +353,7 @@ public class AzureManagementServiceDelegate {
}
LOGGER.info("AzureManagementServiceDelegate: handleCustomScriptExtension: Jenkins server url "+jenkinsServerURL);
// set custom script extension in role
return addResourceExtenions(roleName, template.getStorageAccountName(), storageAccountKey, Constants.CONFIG_CONTAINER_NAME, blobURL, fileName, jenkinsServerURL);
return addResourceExtenions(roleName, template.getStorageAccountName(), storageAccountKey, Constants.CONFIG_CONTAINER_NAME, blobURL, fileName, jenkinsServerURL, jnlpSecret);
} catch (Exception e) {
e.printStackTrace();
throw new AzureCloudException("AzureManagementServiceDelegate: handleCustomScriptExtension: Exception occured while adding custom extension "+e);
@ -359,7 +363,7 @@ public class AzureManagementServiceDelegate {
/** Adds BGInfo and CustomScript extension */
public static ArrayList<ResourceExtensionReference> addResourceExtenions(String roleName, String storageAccountName,
String storageAccountKey, String containerName, String blobURL, String fileName, String jenkinsServerURL) throws Exception {
String storageAccountKey, String containerName, String blobURL, String fileName, String jenkinsServerURL, String jnlpSecret) throws Exception {
ArrayList<ResourceExtensionReference> resourceExtensions = new ArrayList<ResourceExtensionReference>();
// Add custom script extension
@ -394,7 +398,7 @@ public class AzureManagementServiceDelegate {
// }
// LOGGER.info("AzureManagementServiceDelegate: addResourceExtenions: user.getId() "+userID + " API token "+token);
pubicConfig.setValue(getCustomScriptPublicConfigValue(sasURL, fileName, jenkinsServerURL, roleName));
pubicConfig.setValue(getCustomScriptPublicConfigValue(sasURL, fileName, jenkinsServerURL, roleName, jnlpSecret));
pubicConfig.setType("Public");
ResourceExtensionParameterValue privateConfig = new ResourceExtensionParameterValue();
@ -407,8 +411,8 @@ public class AzureManagementServiceDelegate {
}
/** JSON string custom script public config value */
public static String getCustomScriptPublicConfigValue(String sasURL, String fileName, String jenkinsServerURL, String vmName)
throws Exception {
public static String getCustomScriptPublicConfigValue(String sasURL, String fileName, String jenkinsServerURL, String vmName,
String jnlpSecret) throws Exception {
JsonFactory factory = new JsonFactory();
StringWriter stringWriter = new StringWriter();
JsonGenerator json = factory.createJsonGenerator(stringWriter);
@ -418,7 +422,7 @@ public class AzureManagementServiceDelegate {
json.writeString(sasURL);
json.writeEndArray();
json.writeStringField("commandToExecute","powershell -ExecutionPolicy Unrestricted -file " + fileName
+ " " + jenkinsServerURL + " " + vmName);
+ " " + jenkinsServerURL + " " + vmName + " " + jnlpSecret);
json.writeEndObject();
json.close();
return stringWriter.toString();

View File

@ -51,45 +51,44 @@ public class AzureSlavePostBuildAction extends Recorder {
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws InterruptedException, IOException {
if (build.getResult() != Result.SUCCESS) {
LOGGER.info("AzureSlavePostBuildAction: perform: build is not successful , taking post build action "+slavePostBuildAction+" for slave ");
Node node = Computer.currentComputer().getNode();
int retryCount = 0;
boolean successfull = false;
// Retrying for 30 times with 30 seconds wait time between each retry
while (retryCount < 30 && !successfull) {
try {
//check if node is instance of azure slave
if (node instanceof AzureSlave) {
AzureSlave slave = (AzureSlave)node;
if (slave.getChannel() != null) {
slave.getChannel().close();
}
if (Messages.Shutdown_Slave_If_Not_Success().equalsIgnoreCase(slavePostBuildAction)) {
slave.setShutdownOnIdle(true);
slave.idleTimeout();
} else {
slave.setShutdownOnIdle(false);
slave.idleTimeout();
}
}
successfull = true;
} catch (Exception e) {
retryCount++;
LOGGER.info("AzureSlavePostBuildAction: perform: Exception occured while " + slavePostBuildAction + "\n"
+ "Will retry again after 30 seconds. Current retry count "+retryCount + "\n"
+ "Error code "+e.getMessage());
// We won't get exception for RNF , so for other exception types we can retry
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
LOGGER.info("AzureSlavePostBuildAction: perform: build is not successful , taking post build action "+slavePostBuildAction+" for slave ");
Node node = Computer.currentComputer().getNode();
int retryCount = 0;
boolean successfull = false;
// Retrying for 30 times with 30 seconds wait time between each retry
while (retryCount < 30 && !successfull) {
try {
//check if node is instance of azure slave
if (node instanceof AzureSlave) {
AzureSlave slave = (AzureSlave)node;
if (slave.getChannel() != null) {
slave.getChannel().close();
}
if (Messages.Build_Action_Shutdown_Slave().equalsIgnoreCase(slavePostBuildAction)) {
slave.setShutdownOnIdle(true);
slave.idleTimeout();
} else if (Messages.Build_Action_Delete_Slave().equalsIgnoreCase(slavePostBuildAction)
|| (Messages.Build_Action_Delete_Slave_If_Not_Success().equalsIgnoreCase(slavePostBuildAction) && build.getResult() != Result.SUCCESS)) {
slave.setShutdownOnIdle(false);
slave.idleTimeout();
}
}
}
}
successfull = true;
} catch (Exception e) {
retryCount++;
LOGGER.info("AzureSlavePostBuildAction: perform: Exception occured while " + slavePostBuildAction + "\n"
+ "Will retry again after 30 seconds. Current retry count "+retryCount + "\n"
+ "Error code "+e.getMessage());
// We won't get exception for RNF , so for other exception types we can retry
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
return true;
}
@ -108,8 +107,9 @@ public class AzureSlavePostBuildAction extends Recorder {
public ListBoxModel doFillSlavePostBuildActionItems() {
ListBoxModel model = new ListBoxModel();
model.add(Messages.Shutdown_Slave_If_Not_Success());
model.add(Messages.Delete_Slave_If_Not_Success());
model.add(Messages.Build_Action_Shutdown_Slave());
model.add(Messages.Build_Action_Delete_Slave());
model.add(Messages.Build_Action_Delete_Slave_If_Not_Success());
return model;
}

View File

@ -35,19 +35,45 @@ public class AzureUtil {
// Although ugly to maintain this is best way for now.
public static String DEFAULT_INIT_SCRIPT = "Set-ExecutionPolicy Unrestricted"+ "\n" +
"$jenkinsserverurl = $args[0]"+ "\n" + "$vmname = $args[1]" + "\n"+
"$source = \"http://azure.azulsystems.com/zulu/zulu1.7.0_51-7.3.0.4-win64.zip?jenkins\""+ "\n" +
"mkdir c:\\azurecsdir" + "\n" + "$destination = \"c:\\azurecsdir\\zuluJDK.zip\""+ "\n" +
"$wc = New-Object System.Net.WebClient "+ "\n" + "$wc.DownloadFile($source, $destination)"+ "\n" +
"$shell_app=new-object -com shell.application" + "\n" + "$zip_file = $shell_app.namespace($destination)" + "\n" +
"mkdir c:\\java" + "\n" + "$destination = $shell_app.namespace(\"c:\\java\")"+ "\n" + "$destination.Copyhere($zip_file.items())" + "\n" +
"$slaveSource = $jenkinsserverurl + \"jnlpJars/slave.jar\"" + "\n" + "$destSource = \"c:\\java\\slave.jar\"" + "\n" +
"$wc = New-Object System.Net.WebClient" + "\n" + "$wc.DownloadFile($slaveSource, $destSource)" + "\n" +
"$java=\"c:\\java\\zulu1.7.0_51-7.3.0.4-win64\\bin\\java.exe\"" + "\n" + "$jar=\"-jar\"" + "\n" +
"$jnlpUrl=\"-jnlpUrl\""+ "\n" + "$serverURL=$jenkinsserverurl+\"computer/\" + $vmname + \"/slave-agent.jnlp\""+ "\n" +
"& $java $jar $destSource $jnlpUrl $serverURL";
public static String DEFAULT_INIT_SCRIPT = "Set-ExecutionPolicy Unrestricted"+ "\n" +
"$jenkinsServerUrl = $args[0]"+ "\n" +
"$vmName = $args[1]" + "\n" +
"$secret = $args[2]" + "\n" +
"$jenkinsSlaveJarUrl = $jenkinsServerUrl + \"jnlpJars/slave.jar\"" + "\n" +
"$jnlpUrl=$jenkinsServerUrl + 'computer/' + $vmName + '/slave-agent.jnlp'" + "\n" +
"$baseDir = 'c:\\azurecsdir'" + "\n" +
"$JDKUrl = 'http://azure.azulsystems.com/zulu/zulu1.7.0_51-7.3.0.4-win64.zip?jenkins'" + "\n" +
"$destinationJDKZipPath = $baseDir + '\\zuluJDK.zip'" + "\n" +
"$destinationSlaveJarPath = $baseDir + '\\slave.jar'" + "\n" +
"$javaExe = $baseDir + '\\zulu1.7.0_51-7.3.0.4-win64\\bin\\java.exe'" + "\n" +
"function Get-ScriptPath" + "\n" + "{" + "\n" +
"return $MyInvocation.ScriptName;" + "\n" + "}" + "\n" +
"If(-not((Test-Path $destinationJDKZipPath)))" + "\n" + "{" + "\n" +
"md -Path $baseDir -Force" + "\n" +
"$wc = New-Object System.Net.WebClient" + "\n" +
"$wc.DownloadFile($JDKUrl, $destinationJDKZipPath)" + "\n" +
"$shell_app = new-object -com shell.application" + "\n" +
"$zip_file = $shell_app.namespace($destinationJDKZipPath)" + "\n" +
"$javaInstallDir = $shell_app.namespace($baseDir)" + "\n" +
"$javaInstallDir.Copyhere($zip_file.items())" + "\n" +
"$wc = New-Object System.Net.WebClient" + "\n" +
"$wc.DownloadFile($jenkinsSlaveJarUrl, $destinationSlaveJarPath)" + "\n" +
"$scriptPath = Get-ScriptPath" + "\n" +
"$content = 'powershell.exe -ExecutionPolicy Unrestricted -file' + ' '+ $scriptPath + ' '+ $jenkinsServerUrl + ' ' + $vmName + ' ' + $secret" + "\n" +
"schtasks /create /tn \"Jenkins slave agent\" /ru \"SYSTEM\" /sc onstart /rl HIGHEST /delay 0000:30 /tr $content /f" + "\n" +
"$scriptPath = Get-ScriptPath" + "\n" + "}" + "\n" +
"$process = New-Object System.Diagnostics.Process;" + "\n" +
"$process.StartInfo.FileName = $javaExe;" + "\n" +
"If($secret)" + "\n" + "{" + "\n" +
"$process.StartInfo.Arguments = \"-jar $destinationSlaveJarPath -secret $secret -jnlpUrl $jnlpUrl\"" + "\n" + "}" + "\n" + "else" + "\n" + "{" + "\n" +
"$process.StartInfo.Arguments = \"-jar $destinationSlaveJarPath -jnlpUrl $jnlpUrl\"" + "\n" + "}" + "\n" +
"$process.StartInfo.RedirectStandardError = $true;" + "\n" +
"$process.StartInfo.RedirectStandardOutput = $true;" + "\n" +
"$process.StartInfo.UseShellExecute = $false;" + "\n" +
"$process.StartInfo.CreateNoWindow = $true;" + "\n" +
"$process.StartInfo;" + "\n" +
"$process.Start();" + "\n" ;
/** Converts bytes to hex representation */
public static String hexify(byte bytes[]) {
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',

View File

@ -30,6 +30,10 @@
<f:textbox default="60" />
</f:entry>
<f:entry title="${%shutdownOnIdle}" field="shutdownOnIdle" help="/plugin/azure-slave-plugin/help-shutdownOnIdle.html">
<f:checkbox/>
</f:entry>
<f:slave-mode name="useSlaveAlwaysIfAvail" node="${instance}" help="/plugin/azure-slave-plugin/help-slaveMode.html" default="EXCLUSIVE"/>
</f:section>
@ -41,7 +45,7 @@
<f:entry title="${%Launch_Method}" field="slaveLaunchMethod" help="/plugin/azure-slave-plugin/help-slaveLaunchMethod.html">
<f:select />
</f:entry>
<f:entry title="${%Init_Script}" field="initScript" help="/plugin/azure-slave-plugin/help-initScript.html">
<f:textarea />
</f:entry>
@ -79,7 +83,7 @@
<f:entry title="${%noOfParallelJobs}" field="noOfParallelJobs" help="/plugin/azure-slave-plugin/help-noOfParallelJobs.html">
<f:textbox default="${descriptor.getDefaultNoOfExecutors()}" />
</f:entry>
<f:entry title="${%Template_Status}" field="templateStatus" help="/plugin/azure-slave-plugin/help-templateStatus.html">
<f:select />
</f:entry>

View File

@ -9,7 +9,7 @@ noOfParallelJobs=Number of Executors
Image_Configuration=Image Configuration
Image_Family_Or_ID=Image Family or Id
Launch_Method=Launch method
Launch_Method=Launch Method
Init_Script=Init Script
Username=Username
Password=Password
@ -23,4 +23,5 @@ Delete_Template=Delete Template
Verify_Template=Verify Template
Verifying_Template_MSG=Verifying Template, this may take time. Please wait...
VirtualNetwork_Name=Virtual Network Name
Subnet_Name=Subnet Name
Subnet_Name=Subnet Name
shutdownOnIdle=Shutdown Only (Do Not Delete) After Retention Time

View File

@ -46,6 +46,7 @@ Slave_Failed_To_Connect=The slave failed to connect. The node has been marked fo
# Post build action for deprovisioning
Azure_Slave_Post_Build_Action=Configure a post build action for the Azure slave.
Shutdown_Slave_If_Not_Success=Shut down the Azure slave if the build is not successful.
Delete_Slave_If_Not_Success=Delete the Azure slave if the build is not successful.
Build_Action_Shutdown_Slave=Shutdown Azure slave.
Build_Action_Delete_Slave=Delete Azure slave after job execution.
Build_Action_Delete_Slave_If_Not_Success=Delete Azure slave if the build is not successful.
SA_Blank_Create_New=(Leave blank to create a new storage account)

View File

@ -0,0 +1,60 @@
Set-ExecutionPolicy Unrestricted
$jenkinsServerUrl = $args[0]
$vmName = $args[1]
$secret = $args[2]
$jenkinsSlaveJarUrl = $jenkinsServerUrl + "jnlpJars/slave.jar"
$jnlpUrl=$jenkinsServerUrl + 'computer/' + $vmName + '/slave-agent.jnlp'
$baseDir = 'c:\azurecsdir'
$JDKUrl = 'http://azure.azulsystems.com/zulu/zulu1.7.0_51-7.3.0.4-win64.zip?jenkins'
$destinationJDKZipPath = $baseDir + '\zuluJDK.zip'
$destinationSlaveJarPath = $baseDir + '\slave.jar'
$javaExe = $baseDir + '\zulu1.7.0_51-7.3.0.4-win64\bin\java.exe'
# Function to get path of script file
function Get-ScriptPath
{
return $MyInvocation.ScriptName;
}
# Checking if this is first time script is getting executed, if yes then downloading JDK
If(-not((Test-Path $destinationJDKZipPath)))
{
md -Path $baseDir -Force
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($JDKUrl, $destinationJDKZipPath)
$shell_app = new-object -com shell.application
$zip_file = $shell_app.namespace($destinationJDKZipPath)
$javaInstallDir = $shell_app.namespace($baseDir)
$javaInstallDir.Copyhere($zip_file.items())
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($jenkinsSlaveJarUrl, $destinationSlaveJarPath)
$scriptPath = Get-ScriptPath
$content = 'powershell.exe -ExecutionPolicy Unrestricted -file' + ' '+ $scriptPath + ' '+ $jenkinsServerUrl + ' ' + $vmName + ' ' + $secret
schtasks /create /tn "Jenkins slave agent" /ru "SYSTEM" /sc onstart /rl HIGHEST /delay 0000:30 /tr $content /f
}
# Launching jenkins slave agent
$process = New-Object System.Diagnostics.Process;
$process.StartInfo.FileName = $javaExe;
If($secret)
{
$process.StartInfo.Arguments = "-jar $destinationSlaveJarPath -secret $secret -jnlpUrl $jnlpUrl"
}
else
{
$process.StartInfo.Arguments = "-jar $destinationSlaveJarPath -jnlpUrl $jnlpUrl"
}
$process.StartInfo.RedirectStandardError = $true;
$process.StartInfo.RedirectStandardOutput = $true;
$process.StartInfo.UseShellExecute = $false;
$process.StartInfo.CreateNoWindow = $true;
$process.StartInfo;
$process.Start();
Write-Host 'Done Init Script.';

View File

@ -1,4 +1,4 @@
<div>
Number of minutes Jenkins can wait before deleting an idle slave.
<b>Specify 0 if you do not want slaves to be deleted.</b>
Number of minutes for Jenkins to wait before deleting or shutting down an idle slave.
<b>Specify 0 if you want the slave to keep running.</b>
</div>

View File

@ -0,0 +1,3 @@
<div>
By default, after the specified retention time, the slave will be deleted. Checking this option will shut down the slave instead of deleting.
</div>