Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
21 changes: 21 additions & 0 deletions src/Compute/Compute.Test/ScenarioTests/StrategiesVmssTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,26 @@ public void SimpleNewVmssSkipExtOverprovision()
{
TestRunner.RunTestScript("Test-SimpleNewVmssSkipExtOverprovision");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestVmssLifecycleHookConfig()
{
TestRunner.RunTestScript("Test-VmssLifecycleHookConfig");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestSetVmssLifecycleHooksProfile()
{
TestRunner.RunTestScript("Test-SetVmssLifecycleHooksProfile");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestNewVmssConfigWithLifecycleHooksProfile()
{
TestRunner.RunTestScript("Test-NewVmssConfigWithLifecycleHooksProfile");
}
}
}
71 changes: 71 additions & 0 deletions src/Compute/Compute.Test/ScenarioTests/StrategiesVmssTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,74 @@ function Test-SimpleNewVmssSkipExtOverprovision
Clean-ResourceGroup $vmssname
}
}

<#
.SYNOPSIS
Test New-AzVmssLifecycleHookConfig creates an in-memory lifecycle hook config object.
#>
function Test-VmssLifecycleHookConfig
{
# Test creating a lifecycle hook config object in memory (no Azure calls needed)
$hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H'
Assert-NotNull $hook
Assert-AreEqual 'UpgradeAutoOSScheduling' $hook.Type
Assert-AreEqual ([System.TimeSpan]::FromHours(8)) $hook.WaitDuration
Assert-AreEqual 'Approve' $hook.DefaultAction

# With explicit default action
$hook2 = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSRollingBatchStarting' -WaitDuration 'PT30M' -DefaultAction 'Approve'
Assert-NotNull $hook2
Assert-AreEqual 'UpgradeAutoOSRollingBatchStarting' $hook2.Type
Assert-AreEqual ([System.TimeSpan]::FromMinutes(30)) $hook2.WaitDuration
Assert-AreEqual 'Approve' $hook2.DefaultAction

# Without wait duration
$hook3 = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling'
Assert-NotNull $hook3
Assert-Null $hook3.WaitDuration
Assert-AreEqual 'Approve' $hook3.DefaultAction
}

<#
.SYNOPSIS
Test Set-AzVmssLifecycleHooksProfile attaches hooks to a VMSS config object.
#>
function Test-SetVmssLifecycleHooksProfile
{
# Test attaching lifecycle hooks to an in-memory VMSS config (no Azure calls)
$hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H'
$config = New-AzVmssConfig -Location 'eastus' -SkuCapacity 2

$config = Set-AzVmssLifecycleHooksProfile -VirtualMachineScaleSet $config -LifecycleHook $hook

Assert-NotNull $config
Assert-NotNull $config.LifecycleHooksProfile
Assert-AreEqual 1 $config.LifecycleHooksProfile.LifecycleHooks.Count
Assert-AreEqual 'UpgradeAutoOSScheduling' $config.LifecycleHooksProfile.LifecycleHooks[0].Type

# Test with multiple hooks
$hook2 = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSRollingBatchStarting' -WaitDuration 'PT30M'
$config = Set-AzVmssLifecycleHooksProfile -VirtualMachineScaleSet $config -LifecycleHook @($hook, $hook2)

Assert-AreEqual 2 $config.LifecycleHooksProfile.LifecycleHooks.Count
}

<#
.SYNOPSIS
Test New-AzVmssConfig with -LifecycleHooksProfile parameter.
#>
function Test-NewVmssConfigWithLifecycleHooksProfile
{
# Test creating VMSS config with inline LifecycleHooksProfile
$hook = New-AzVmssLifecycleHookConfig -Type 'UpgradeAutoOSScheduling' -WaitDuration 'PT8H'
$profile = [Microsoft.Azure.Management.Compute.Models.LifecycleHooksProfile]::new()
$profile.LifecycleHooks = [System.Collections.Generic.List[Microsoft.Azure.Management.Compute.Models.LifecycleHook]]::new()
$profile.LifecycleHooks.Add($hook)

$config = New-AzVmssConfig -Location 'eastus' -SkuCapacity 2 -LifecycleHooksProfile $profile

Assert-NotNull $config
Assert-NotNull $config.LifecycleHooksProfile
Assert-AreEqual 1 $config.LifecycleHooksProfile.LifecycleHooks.Count
Assert-AreEqual 'UpgradeAutoOSScheduling' $config.LifecycleHooksProfile.LifecycleHooks[0].Type
}
8 changes: 8 additions & 0 deletions src/Compute/Compute/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@

-->
## Upcoming Release
* Added VMSS (Virtual Machine Scale Set) Lifecycle Hooks support (public preview)
- Added `New-AzVmssLifecycleHookConfig` cmdlet to create an in-memory lifecycle hook configuration object
- Added `Set-AzVmssLifecycleHooksProfile` cmdlet to attach lifecycle hooks to a VMSS configuration or live VMSS object
- Added `Remove-AzVmssLifecycleHook` cmdlet to remove one hook by `-Type` or all hooks with `-All` from a live VMSS
- Added `Get-AzVmssLifecycleHookEvent` cmdlet to list or retrieve lifecycle hook events for a VMSS
- Added `Update-AzVmssLifecycleHookEvent` cmdlet to respond to a lifecycle hook event (approve, reject, or delay) with optional per-VM instance filtering via `-InstanceId`
- Added `-LifecycleHooksProfile` parameter to `New-AzVmssConfig` to support inline lifecycle hooks profile construction
- Note: `-DefaultAction Reject` and `-ActionState Rejected` return a server error during preview; no client change is needed at GA
* Added `-InstantAccess` parameter to `New-AzRestorePointCollection` cmdlet to enable instant access snapshots for restore points on Premium SSD v2 and Ultra disks
* Added `-InstantAccess` parameter to `Update-AzRestorePointCollection` cmdlet to enable or disable instant access on an existing restore point collection
* Added `-InstantAccessDurationInMinutes` parameter to `New-AzRestorePoint` cmdlet to specify the duration (1-300 minutes) for which the instant access snapshot is retained
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ public IVirtualMachineScaleSetVMRunCommandsOperations VirtualMachineScaleSetVMRu
}
}

public IVirtualMachineScaleSetLifeCycleHookEventsOperations VirtualMachineScaleSetLifeCycleHookEventsClient
{
get
{
return ComputeClient.ComputeManagementClient.VirtualMachineScaleSetLifeCycleHookEvents;
}
}

public IVirtualMachinesOperations VirtualMachinesClient
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ public partial class NewAzureRmVmssConfigCommand : Microsoft.Azure.Commands.Reso
[PSArgumentCompleter("None", "Trunk")]
public string HighSpeedInterconnectPlacement { get; set; }

[Parameter(
Mandatory = false,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies the lifecycle hooks profile for the virtual machine scale set. Use Set-AzVmssLifecycleHooksProfile or create a LifecycleHooksProfile object directly.")]
public LifecycleHooksProfile LifecycleHooksProfile { get; set; }

protected override void ProcessRecord()
{
if (ShouldProcess("VirtualMachineScaleSet", "New"))
Expand Down Expand Up @@ -1256,7 +1262,8 @@ private void Run()
SkuProfile = vSkuProfile,
ResiliencyPolicy = vResiliencyPolicy,
Placement = vPlacement,
HighSpeedInterconnectPlacement = this.IsParameterBound(c => c.HighSpeedInterconnectPlacement) ? this.HighSpeedInterconnectPlacement : null
HighSpeedInterconnectPlacement = this.IsParameterBound(c => c.HighSpeedInterconnectPlacement) ? this.HighSpeedInterconnectPlacement : null,
LifecycleHooksProfile = this.IsParameterBound(c => c.LifecycleHooksProfile) ? this.LifecycleHooksProfile : null
};

WriteObject(vVirtualMachineScaleSet);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using System;
using System.Management.Automation;
using System.Xml;
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
using Microsoft.Azure.Management.Compute.Models;

namespace Microsoft.Azure.Commands.Compute.Automation
{
[Cmdlet(VerbsCommon.New, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "VmssLifecycleHookConfig", SupportsShouldProcess = true)]
[OutputType(typeof(LifecycleHook))]
public class NewAzureRmVmssLifecycleHookConfigCommand : Microsoft.Azure.Commands.ResourceManager.Common.AzureRMCmdlet
{
private const string DefaultDefaultAction = "Approve";

[Parameter(
Mandatory = true,
Position = 0,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies the type of the lifecycle hook. Possible values: 'UpgradeAutoOSScheduling', 'UpgradeAutoOSRollingBatchStarting'.")]
[PSArgumentCompleter("UpgradeAutoOSScheduling", "UpgradeAutoOSRollingBatchStarting")]
[ValidateNotNullOrEmpty]
public string Type { get; set; }

[Parameter(
Mandatory = false,
Position = 1,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies the time duration the lifecycle hook event waits for a customer response before applying the default action. Must be in ISO 8601 duration format, for example 'PT8H' (8 hours) or 'PT30M' (30 minutes).")]
public string WaitDuration { get; set; }

[Parameter(
Mandatory = false,
Position = 2,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies the default action applied when the wait duration expires with no customer response. Accepted values: 'Approve' (default), 'Reject'. Note: 'Reject' returns a server error during preview.")]
[PSArgumentCompleter("Approve", "Reject")]
[ValidateSet("Approve", "Reject")]
public string DefaultAction { get; set; }

protected override void ProcessRecord()
{
if (ShouldProcess("VmssLifecycleHookConfig", "New"))
{
TimeSpan? waitDuration = null;
if (!string.IsNullOrEmpty(this.WaitDuration))
{
waitDuration = XmlConvert.ToTimeSpan(this.WaitDuration);
}

var hook = new LifecycleHook
{
Type = this.Type,
WaitDuration = waitDuration,
DefaultAction = string.IsNullOrEmpty(this.DefaultAction) ? DefaultDefaultAction : this.DefaultAction

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@copilot add a comment that "Since currently in production, if the customer does no pass defaultAction, we use "Approve" for these types, this works. In the future if we change the default defaultAction in production for any existing / future types, this needs to change"

};

WriteObject(hook);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.Compute.Automation.Models;
using Microsoft.Azure.Management.Compute.Models;

namespace Microsoft.Azure.Commands.Compute.Automation
{
[Cmdlet(VerbsCommon.Set, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "VmssLifecycleHooksProfile", SupportsShouldProcess = true)]
[OutputType(typeof(PSVirtualMachineScaleSet))]
public class SetAzureRmVmssLifecycleHooksProfileCommand : Microsoft.Azure.Commands.ResourceManager.Common.AzureRMCmdlet
{
[Parameter(
Mandatory = true,
Position = 0,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
HelpMessage = "The VMSS configuration object (PSVirtualMachineScaleSet) to update. Can be an in-memory config or a live VMSS object retrieved with Get-AzVmss.")]
[ValidateNotNull]
public PSVirtualMachineScaleSet VirtualMachineScaleSet { get; set; }

[Parameter(
Mandatory = true,
Position = 1,
ValueFromPipelineByPropertyName = true,
HelpMessage = "One or more lifecycle hook objects to attach to the VMSS. Use New-AzVmssLifecycleHookConfig to create hook objects.")]
[ValidateNotNull]
public LifecycleHook[] LifecycleHook { get; set; }

protected override void ProcessRecord()
{
if (ShouldProcess(this.VirtualMachineScaleSet?.Name ?? "VirtualMachineScaleSet", "Set-AzVmssLifecycleHooksProfile"))
{
this.VirtualMachineScaleSet.LifecycleHooksProfile = new LifecycleHooksProfile
{
LifecycleHooks = this.LifecycleHook.ToList()
};

WriteObject(this.VirtualMachineScaleSet);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
using Microsoft.Azure.Management.Compute;
using Microsoft.Azure.Management.Compute.Models;

namespace Microsoft.Azure.Commands.Compute.Automation
{
[Cmdlet(VerbsCommon.Get, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "VmssLifecycleHookEvent", DefaultParameterSetName = ListParameterSet)]
[OutputType(typeof(VMScaleSetLifecycleHookEvent))]
public class GetAzureRmVmssLifecycleHookEventCommand : ComputeAutomationBaseCmdlet
{
private const string ListParameterSet = "ListParameterSet";
private const string GetParameterSet = "GetParameterSet";

[Parameter(
Mandatory = true,
Position = 0,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ListParameterSet,
HelpMessage = "The name of the resource group.")]
[Parameter(
Mandatory = true,
Position = 0,
ValueFromPipelineByPropertyName = true,
ParameterSetName = GetParameterSet,
HelpMessage = "The name of the resource group.")]
[ResourceGroupCompleter]
[ValidateNotNullOrEmpty]
public string ResourceGroupName { get; set; }

[Parameter(
Mandatory = true,
Position = 1,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ListParameterSet,
HelpMessage = "The name of the VM scale set.")]
[Parameter(
Mandatory = true,
Position = 1,
ValueFromPipelineByPropertyName = true,
ParameterSetName = GetParameterSet,
HelpMessage = "The name of the VM scale set.")]
[ResourceNameCompleter("Microsoft.Compute/virtualMachineScaleSets", "ResourceGroupName")]
[ValidateNotNullOrEmpty]
[Alias("VMScaleSetName")]
public string VMScaleSetName { get; set; }

[Parameter(
Mandatory = true,
Position = 2,
ValueFromPipelineByPropertyName = true,
ParameterSetName = GetParameterSet,
HelpMessage = "The name (GUID) of the lifecycle hook event to retrieve.")]
[ValidateNotNullOrEmpty]
public string Name { get; set; }

public override void ExecuteCmdlet()
{
base.ExecuteCmdlet();
ExecuteClientAction(() =>
{
if (this.ParameterSetName.Equals(GetParameterSet))
{
var result = VirtualMachineScaleSetLifeCycleHookEventsClient.Get(
this.ResourceGroupName,
this.VMScaleSetName,
this.Name);
WriteObject(result);
}
else
{
var result = VirtualMachineScaleSetLifeCycleHookEventsClient.List(
this.ResourceGroupName,
this.VMScaleSetName);
var resultList = result.ToList();
var nextPageLink = result.NextPageLink;
while (!string.IsNullOrEmpty(nextPageLink))
{
var pageResult = VirtualMachineScaleSetLifeCycleHookEventsClient.ListNext(nextPageLink);
foreach (var pageItem in pageResult)
{
resultList.Add(pageItem);
}
nextPageLink = pageResult.NextPageLink;
}
WriteObject(resultList, true);
}
});
}
}
}
Loading
Loading