Skip to content
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

Enabling custom script extension support for VMSS deployment #4645

Merged
merged 6 commits into from
Jun 28, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Tasks/AzureVmssDeployment/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import path = require("path");
import AzureVmssTaskParameters from "./models/AzureVmssTaskParameters";
import VirtualMachineScaleSet from "./operations/VirtualMachineScaleSet";

function run(): Promise<void> {
async function run(): Promise<void> {
var taskParameters = new AzureVmssTaskParameters();
var vmssOperation = new VirtualMachineScaleSet(taskParameters);
switch (taskParameters.action) {
case "UpdateImage":
return vmssOperation.execute();
await vmssOperation.execute();
default:
throw tl.loc("InvalidAction", taskParameters.action);
}
Expand Down
207 changes: 108 additions & 99 deletions Tasks/AzureVmssDeployment/operations/VirtualMachineScaleSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ export default class VirtualMachineScaleSet {
this.taskParameters = taskParameters;
}

public execute(): Promise<void> {
public async execute(): Promise<void> {
var client = new armCompute.ComputeManagementClient(this.taskParameters.credentials, this.taskParameters.subscriptionId);
return new Promise<void>((resolve, reject) => {
this._getResourceGroupForVmss(client,resolve, reject, (error, result, request, response) => {
return new Promise<void>(async (resolve, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove creating and returning this promise as well since your method is async

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. infact removed try-catch also

try {
var result = await this._getResourceGroupForVmss(client);
var resourceGroupName: string = result.resourceGroupName;
var osType: string = result.osType;
if(!resourceGroupName) {
Expand All @@ -24,135 +25,143 @@ export default class VirtualMachineScaleSet {

switch (this.taskParameters.action) {
case "UpdateImage":
var extensionMetadata: azureModel.VMExtensionMetadata = null;
var customScriptExtension: azureModel.VMExtension = null;
if(!!this.taskParameters.customScriptUrl) {
extensionMetadata = this._getCustomScriptExtensionMetadata(osType);
customScriptExtension = {
name: "CustomScriptExtension" + Date.now().toString(),
properties: {
type: extensionMetadata.type,
publisher: extensionMetadata.publisher,
typeHandlerVersion: extensionMetadata.typeHandlerVersion,
autoUpgradeMinorVersion: true,
settings: {
"fileUris": [ this.taskParameters.customScriptUrl ]
},
protectedSettings: {
"commandToExecute": this.taskParameters.customScriptCommand
var extensionMetadata: azureModel.VMExtensionMetadata = null;
var customScriptExtension: azureModel.VMExtension = null;
if(!!this.taskParameters.customScriptUrl) {
extensionMetadata = this._getCustomScriptExtensionMetadata(osType);
customScriptExtension = {
name: "CustomScriptExtension" + Date.now().toString(),
properties: {
type: extensionMetadata.type,
publisher: extensionMetadata.publisher,
typeHandlerVersion: extensionMetadata.typeHandlerVersion,
autoUpgradeMinorVersion: true,
settings: {
"fileUris": [ this.taskParameters.customScriptUrl ]
},
protectedSettings: {
"commandToExecute": this.taskParameters.customScriptCommand
}
}
}
};
};

this._getExistingCustomScriptExtension(client, resourceGroupName, customScriptExtension, (error, matchingExtension, request, response) => {
var matchingExtension = await this._getExistingCustomScriptExtension(client, resourceGroupName, customScriptExtension);
// if extension already exists, remove it
if(!!matchingExtension) {
this._deleteAndInstallCustomScriptExtension(client, resourceGroupName, matchingExtension, customScriptExtension, resolve, reject, (error, matchingExtension, request, response) => {
this._updateImageInternal(client, resourceGroupName, resolve, reject);
});
await this._deleteAndInstallCustomScriptExtension(client, resourceGroupName, matchingExtension, customScriptExtension);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: similarly consider if you need to merge this method now. Or you can delete if a matching extension is found. Install extension always. And then update image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

await this._updateImageInternal(client, resourceGroupName);
} else {
this._installCustomScripExtension(client, resourceGroupName, customScriptExtension, resolve, reject, (error, result, request, response) => {
this._updateImageInternal(client, resourceGroupName, resolve, reject);
});
await this._installCustomScripExtension(client, resourceGroupName, customScriptExtension);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my knowledge, is this the right way to update image (to do it in two steps). Would it lead to smooth upgrade; I thought earlier logic of merged update was better for upgrade; i.e. would it lead to zero downtime?

Copy link
Contributor Author

@bishal-pdMSFT bishal-pdMSFT Jun 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there were few reasons why I did like this:

  1. deleting & installing extension before image update will not cause any downtime. The custom script is supposed to run at start-up only. Removing extension is not going to have any adverse affect.
  2. I was seeing some reliability issues when earlier I was doing both in same PATCH call. it used to hang sometimes especially when extension was failed previously.
  3. Discussed with Roopesh and this looks better way to achieve blue-green deployment. A cloned VMSS will be used to update extension and image and then vip swap will be done. So no downtime.
  4. Anyways we have to support setting extension as a separate functionality. This makes it re-usable to enable that easily.

await this._updateImageInternal(client, resourceGroupName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Consider not repeating it now. Since this is all await. You can call it once (outside of all conditions)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
});
} else {
this._updateImageInternal(client, resourceGroupName, resolve, reject);
}

break;
} else {
await this._updateImageInternal(client, resourceGroupName);
}
break;
}
});
} catch(error) {
reject(error);
}
});
}

private _getResourceGroupForVmss(client, resolve, reject, callback): void {
client.virtualMachineScaleSets.list(null, (error, result, request, response) => {
if (error) {
return reject(tl.loc("VMSSListFetchFailed", this.taskParameters.vmssName, utils.getError(error)));
}
private _getResourceGroupForVmss(client): Promise<any> {
return new Promise<any>((resolve, reject) => {
client.virtualMachineScaleSets.list(null, (error, result, request, response) => {
if (error) {
return reject(tl.loc("VMSSListFetchFailed", this.taskParameters.vmssName, utils.getError(error)));
}

var vmssList: azureModel.VMSS[] = result;
if (vmssList.length == 0) {
console.log(tl.loc("NoVMSSFound", this.taskParameters.vmssName));
return resolve();
}
var vmssList: azureModel.VMSS[] = result;
if (vmssList.length == 0) {
console.log(tl.loc("NoVMSSFound", this.taskParameters.vmssName));
return resolve();
}

var resourceGroupName: string;
var osType: string;
for (var i = 0; i < vmssList.length; i++) {
if(vmssList[i].name.toUpperCase() === this.taskParameters.vmssName.toUpperCase())
{
resourceGroupName = utils.getResourceGroupNameFromUri(vmssList[i].id);
osType = vmssList[i].properties.virtualMachineProfile.storageProfile.osDisk.osType;
break;
var resourceGroupName: string;
var osType: string;
for (var i = 0; i < vmssList.length; i++) {
if(vmssList[i].name.toUpperCase() === this.taskParameters.vmssName.toUpperCase())
{
resourceGroupName = utils.getResourceGroupNameFromUri(vmssList[i].id);
osType = vmssList[i].properties.virtualMachineProfile.storageProfile.osDisk.osType;
break;
}
}
}

callback(null, { resourceGroupName: resourceGroupName, osType: osType });
return resolve({ resourceGroupName: resourceGroupName, osType: osType });
});
});
}

private _getExistingCustomScriptExtension(client, resourceGroupName, customScriptExtension, callback): void {
client.virtualMachineExtensions.list(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, null, (error, result, request, response) => {
if (error) {
// Just log warning, do not fail
tl.warning(tl.loc("GetVMSSExtensionsListFailed", this.taskParameters.vmssName, utils.getError(error)));
}

var extensions: azureModel.VMExtension[] = result || [];
var matchingExtension: azureModel.VMExtension = null;
extensions.forEach((extension: azureModel.VMExtension) => {
if(extension.properties.type === customScriptExtension.properties.type &&
extension.properties.publisher === customScriptExtension.properties.publisher) {
matchingExtension = extension;
return;
private _getExistingCustomScriptExtension(client, resourceGroupName, customScriptExtension): Promise<azureModel.VMExtension> {
return new Promise<azureModel.VMExtension>((resolve, reject) => {
client.virtualMachineExtensions.list(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, null, (error, result, request, response) => {
if (error) {
// Just log warning, do not fail
tl.warning(tl.loc("GetVMSSExtensionsListFailed", this.taskParameters.vmssName, utils.getError(error)));
}
});

callback(null, matchingExtension);
var extensions: azureModel.VMExtension[] = result || [];
var matchingExtension: azureModel.VMExtension = null;
extensions.forEach((extension: azureModel.VMExtension) => {
if(extension.properties.type === customScriptExtension.properties.type &&
extension.properties.publisher === customScriptExtension.properties.publisher) {
matchingExtension = extension;
return;
}
});

return resolve(matchingExtension);
});
});
}

private _deleteAndInstallCustomScriptExtension(client, resourceGroupName, oldExtension, newExtension, resolve, reject, callback): void {
console.log(tl.loc("RemovingCustomScriptExtension", oldExtension.name));
client.virtualMachineExtensions.deleteMethod(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, oldExtension.name, (error, result, request, response) => {
if (error) {
// Just log warning, do not fail
tl.warning(tl.loc("RemoveVMSSExtensionsFailed", oldExtension.name, utils.getError(error)));
} else {
console.log(tl.loc("CustomScriptExtensionRemoved", oldExtension.name));
}

client.virtualMachineExtensions.createOrUpdate(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, newExtension.name, newExtension, (error, result, request, response) => {
private _deleteAndInstallCustomScriptExtension(client, resourceGroupName, oldExtension, newExtension): Promise<void> {
return new Promise<void>((resolve, reject) => {
console.log(tl.loc("RemovingCustomScriptExtension", oldExtension.name));
client.virtualMachineExtensions.deleteMethod(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, oldExtension.name, (error, result, request, response) => {
if (error) {
return reject(tl.loc("SettingVMExtensionFailed", utils.getError(error)));
// Just log warning, do not fail
tl.warning(tl.loc("RemoveVMSSExtensionsFailed", oldExtension.name, utils.getError(error)));
} else {
console.log(tl.loc("CustomScriptExtensionRemoved", oldExtension.name));
}

console.log(tl.loc("CustomScriptExtensionInstalled", newExtension.name));
callback(null, null);
client.virtualMachineExtensions.createOrUpdate(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, newExtension.name, newExtension, (error, result, request, response) => {
if (error) {
return reject(tl.loc("SettingVMExtensionFailed", utils.getError(error)));
}

console.log(tl.loc("CustomScriptExtensionInstalled", newExtension.name));
return resolve();
});
});
});
}

private _installCustomScripExtension(client, resourceGroupName, customScriptExtension, resolve, reject, callback): void {
client.virtualMachineExtensions.createOrUpdate(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, customScriptExtension.name, customScriptExtension, (error, result, request, response) => {
if (error) {
return reject(tl.loc("SettingVMExtensionFailed", utils.getError(error)));
}
private _installCustomScripExtension(client, resourceGroupName, customScriptExtension): Promise<void> {
return new Promise<void>((resolve, reject) => {
client.virtualMachineExtensions.createOrUpdate(resourceGroupName, this.taskParameters.vmssName, azureModel.ComputeResourceType.VirtualMachineScaleSet, customScriptExtension.name, customScriptExtension, (error, result, request, response) => {
if (error) {
return reject(tl.loc("SettingVMExtensionFailed", utils.getError(error)));
}

console.log(tl.loc("CustomScriptExtensionInstalled", customScriptExtension.name));
callback(null, null);
console.log(tl.loc("CustomScriptExtensionInstalled", customScriptExtension.name));
return resolve();
});
});
}

private _updateImageInternal(client, resourceGroupName, resolve, reject) {
client.virtualMachineScaleSets.updateImage(resourceGroupName, this.taskParameters.vmssName, this.taskParameters.imageUrl, null, (error, result, request, response) => {
if (error) {
return reject(tl.loc("VMSSImageUpdateFailed", utils.getError(error)));
}
console.log(tl.loc("UpdatedVMSSImage"));
return resolve();
private _updateImageInternal(client, resourceGroupName): Promise<void> {
return new Promise<void>((resolve, reject) => {
client.virtualMachineScaleSets.updateImage(resourceGroupName, this.taskParameters.vmssName, this.taskParameters.imageUrl, null, (error, result, request, response) => {
if (error) {
return reject(tl.loc("VMSSImageUpdateFailed", utils.getError(error)));
}
console.log(tl.loc("UpdatedVMSSImage"));
return resolve();
});
});
}

Expand Down