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

Conversation

bishal-pdMSFT
Copy link
Contributor

Setting extension is done separately from updating image(earlier it was part of same PATCH call). Flow is:

  1. List VM extensions for VMSS
  2. Check if custom script extension exists
  3. If so, delete existing extension
  4. Install extension
  5. Update VMSS image

@@ -380,6 +380,7 @@
"OnlyTokenAuthAllowed": "Endpoint can be of Token authorization type only.",
"DeploymentGroupEndpointUrlCannotBeEmpty": "Deployment group endpoint url cannot be empty",
"DeploymentGroupEndpointPatTokenCannotBeEmpty": "Deployment group endpoint personal access token cannot be empty",
"ErrorWhileParsingParameter": "There was an error while overriding '%s' parameter because of '%s'."
"ErrorWhileParsingParameter": "There was an error while overriding '%s' parameter because of '%s'.",
"ResourceNameCannotBeNull": "Resource name cannot be null."
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this string used in ARG task?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is used in common azure-arm-rest. currently, common modules' resource strings are also loaded from task.json.

var httpRequest = new azureServiceClient.WebRequest();
httpRequest.method = 'GET';
httpRequest.headers = this.client.setCustomHeaders(options);
httpRequest.uri = this.client.getRequestUri('//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/{resourceType}/{resourceName}/extensions',
Copy link
Contributor

@tejasd1990 tejasd1990 Jun 27, 2017

Choose a reason for hiding this comment

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

Should the strings "virtualMachines" and "virtualMachineScaleSets" be local to methods in this file only, rather than each of their callers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add an enum

if (result && result.properties.provisioningState === "Failed") {
await this.tryDeleteFailedExtension(vm);
}
console.log(tl.loc("AddExtension", extensionName, vmName));
this.computeClient.virtualMachineExtensions.createOrUpdate(this.taskParameters.resourceGroupName, vmName, extensionName, parameters, async (error, result, request, response) => {
this.computeClient.virtualMachineExtensions.createOrUpdate(this.taskParameters.resourceGroupName, vmName, "virtualMachines",extensionName, parameters, async (error, result, request, response) => {
Copy link
Member

Choose a reason for hiding this comment

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

define "virtualmachines" as a constant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will add an enum

"module": "commonjs"
"module": "commonjs",
"sourceMap": true,
"sourceRoot": "C:\\github\\vsts-tasks\\Tasks\\AzureVmssDeployment"
Copy link
Member

Choose a reason for hiding this comment

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

what is the significance of this hard-coded path. Is this only for IDE. Should we check-in this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is only for debugging. will remove.

"name": "customScriptUrl",
"type": "string",
"label": "Custom script url",
"defaultValue": "",
Copy link
Member

Choose a reason for hiding this comment

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

I thought you were going with the local file assumption

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that will come later.

@@ -62,21 +62,74 @@ export default class VirtualMachineScaleSet {
}
}
}

client.virtualMachineExtensions.list(resourceGroupName, this.taskParameters.vmssName, "virtualMachineScaleSets", null, (error, result, request, response) => {
Copy link
Member

Choose a reason for hiding this comment

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

consider splitting it into another method for better read-ability

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

@@ -386,7 +386,60 @@ export class VirtualMachineExtensions {
this.client = client;
}

public get(resourceGroupName, vmName, vmExtensionName, options, callback) {
public list(resourceGroupName, resourceName, resourceType, options, callback: azureServiceClient.ApiCallback) {
Copy link
Member

Choose a reason for hiding this comment

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

define types for the parameters

break;
}
});
});
}

private _getResourceGroupForVmss(client, resolve, reject, callback): void {
Copy link
Member

Choose a reason for hiding this comment

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

Consider modeling this function as returning a promise instead of using callbacks. We used callback in the library to match the pattern with arm sdk

Copy link
Contributor Author

Choose a reason for hiding this comment

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

public method in this class already uses promise. For other private methods which are calling SDK, I don't see any benefit in wrapping then in promise.

Copy link
Member

Choose a reason for hiding this comment

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

You are right; however I think that will improve the code readability. For example your main method will not have nested statements and be more readable and less error-prone like following.
var resourceGroupDetails = await this.getResourceGroupName;
var extensionStatus = await this.getExistingExtensions();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, readability will increase.


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

Choose a reason for hiding this comment

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

Shouldn't you reject with this message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think no. If no VMSS found, task will not do anything. I will ask Atul.

Copy link
Member

Choose a reason for hiding this comment

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

I will prefer the task to fail if the vmss is not foun

this._updateImageInternal(client, resourceGroupName, resolve, reject);
});
await this._installCustomScripExtension(client, resourceGroupName, customScriptExtension);
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

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

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

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.

Copy link
Member

@sachinma sachinma left a comment

Choose a reason for hiding this comment

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

Rest looks fine. However, lets discuss the upgrade approach that you shared tomorrow

@bishal-pdMSFT bishal-pdMSFT merged commit ad914d3 into master Jun 28, 2017
@bryanmacfarlane bryanmacfarlane deleted the users/biprasad/VmssExtension branch January 31, 2018 00:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants