-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Conversation
@@ -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." |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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": "", |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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();
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- 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.
- 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.
- 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.
- Anyways we have to support setting extension as a separate functionality. This makes it re-usable to enable that easily.
There was a problem hiding this 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
Setting extension is done separately from updating image(earlier it was part of same PATCH call). Flow is: